bibsync 0.3.5__tar.gz → 0.4.0__tar.gz

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.
Files changed (52) hide show
  1. {bibsync-0.3.5 → bibsync-0.4.0}/.pre-commit-config.yaml +2 -2
  2. {bibsync-0.3.5 → bibsync-0.4.0}/CITATION.cff +2 -2
  3. {bibsync-0.3.5 → bibsync-0.4.0}/Cargo.lock +55 -262
  4. {bibsync-0.3.5 → bibsync-0.4.0}/Cargo.toml +2 -2
  5. {bibsync-0.3.5 → bibsync-0.4.0}/PKG-INFO +23 -6
  6. {bibsync-0.3.5 → bibsync-0.4.0}/README.md +22 -5
  7. {bibsync-0.3.5 → bibsync-0.4.0}/docs/user-guide/usage.md +32 -2
  8. {bibsync-0.3.5 → bibsync-0.4.0}/src/cli.rs +2 -2
  9. {bibsync-0.3.5 → bibsync-0.4.0}/src/lib.rs +221 -8
  10. {bibsync-0.3.5 → bibsync-0.4.0}/tests/cli.rs +27 -0
  11. {bibsync-0.3.5 → bibsync-0.4.0}/.gitignore +0 -0
  12. {bibsync-0.3.5 → bibsync-0.4.0}/.markdownlint.yaml +0 -0
  13. {bibsync-0.3.5 → bibsync-0.4.0}/.pre-commit-hooks.yaml +0 -0
  14. {bibsync-0.3.5 → bibsync-0.4.0}/.prettierrc +0 -0
  15. {bibsync-0.3.5 → bibsync-0.4.0}/.typos.toml +0 -0
  16. {bibsync-0.3.5 → bibsync-0.4.0}/CODE_OF_CONDUCT.md +0 -0
  17. {bibsync-0.3.5 → bibsync-0.4.0}/CONTRIBUTING.md +0 -0
  18. {bibsync-0.3.5 → bibsync-0.4.0}/LICENSE +0 -0
  19. {bibsync-0.3.5 → bibsync-0.4.0}/SECURITY.md +0 -0
  20. {bibsync-0.3.5 → bibsync-0.4.0}/SUPPORT.md +0 -0
  21. {bibsync-0.3.5 → bibsync-0.4.0}/benches/greeting.rs +0 -0
  22. {bibsync-0.3.5 → bibsync-0.4.0}/cliff.toml +0 -0
  23. {bibsync-0.3.5 → bibsync-0.4.0}/deny.toml +0 -0
  24. {bibsync-0.3.5 → bibsync-0.4.0}/docs/api/index.md +0 -0
  25. {bibsync-0.3.5 → bibsync-0.4.0}/docs/contributing.md +0 -0
  26. {bibsync-0.3.5 → bibsync-0.4.0}/docs/index.md +0 -0
  27. {bibsync-0.3.5 → bibsync-0.4.0}/docs/security.md +0 -0
  28. {bibsync-0.3.5 → bibsync-0.4.0}/docs/user-guide/examples.md +0 -0
  29. {bibsync-0.3.5 → bibsync-0.4.0}/docs/user-guide/installation.md +0 -0
  30. {bibsync-0.3.5 → bibsync-0.4.0}/docs/user-guide/pre-commit.md +0 -0
  31. {bibsync-0.3.5 → bibsync-0.4.0}/docs/user-guide/providers.md +0 -0
  32. {bibsync-0.3.5 → bibsync-0.4.0}/examples/README.md +0 -0
  33. {bibsync-0.3.5 → bibsync-0.4.0}/examples/greet.rs +0 -0
  34. {bibsync-0.3.5 → bibsync-0.4.0}/examples/inspire-main.bib +0 -0
  35. {bibsync-0.3.5 → bibsync-0.4.0}/examples/inspire-main.tex +0 -0
  36. {bibsync-0.3.5 → bibsync-0.4.0}/examples/main.bib +0 -0
  37. {bibsync-0.3.5 → bibsync-0.4.0}/examples/main.tex +0 -0
  38. {bibsync-0.3.5 → bibsync-0.4.0}/pyproject.toml +0 -0
  39. {bibsync-0.3.5 → bibsync-0.4.0}/python/bibsync/__init__.py +0 -0
  40. {bibsync-0.3.5 → bibsync-0.4.0}/python/bibsync/__init__.pyi +0 -0
  41. {bibsync-0.3.5 → bibsync-0.4.0}/python/bibsync/__main__.py +0 -0
  42. {bibsync-0.3.5 → bibsync-0.4.0}/python/bibsync/py.typed +0 -0
  43. {bibsync-0.3.5 → bibsync-0.4.0}/renovate.json +0 -0
  44. {bibsync-0.3.5 → bibsync-0.4.0}/rust-toolchain.toml +0 -0
  45. {bibsync-0.3.5 → bibsync-0.4.0}/scripts/pre-commit-bibsync +0 -0
  46. {bibsync-0.3.5 → bibsync-0.4.0}/setup_repo.sh +0 -0
  47. {bibsync-0.3.5 → bibsync-0.4.0}/src/main.rs +0 -0
  48. {bibsync-0.3.5 → bibsync-0.4.0}/src/python.rs +0 -0
  49. {bibsync-0.3.5 → bibsync-0.4.0}/tests/examples.rs +0 -0
  50. {bibsync-0.3.5 → bibsync-0.4.0}/tests/python/test_bindings.py +0 -0
  51. {bibsync-0.3.5 → bibsync-0.4.0}/uv.lock +0 -0
  52. {bibsync-0.3.5 → bibsync-0.4.0}/zensical.toml +0 -0
@@ -31,14 +31,14 @@ repos:
31
31
  pass_filenames: false
32
32
 
33
33
  - repo: https://github.com/astral-sh/ruff-pre-commit
34
- rev: v0.15.16
34
+ rev: v0.15.18
35
35
  hooks:
36
36
  - id: ruff
37
37
  args: [--fix, --config=pyproject.toml]
38
38
  - id: ruff-format
39
39
 
40
40
  - repo: https://github.com/astral-sh/uv-pre-commit
41
- rev: 0.11.19
41
+ rev: 0.11.23
42
42
  hooks:
43
43
  - id: uv-lock
44
44
 
@@ -1,7 +1,7 @@
1
1
  cff-version: 1.2.0
2
2
  title: "bibsync: A Rust package to automatically resolve, synchronize, and validate LaTeX citations across BibTeX databases"
3
- version: "0.3.5"
4
- date-released: "2026-06-09"
3
+ version: "0.4.0"
4
+ date-released: "2026-06-22"
5
5
  license: "BSD-3-Clause"
6
6
  type: software
7
7
  authors:
@@ -61,12 +61,6 @@ dependencies = [
61
61
  "windows-sys 0.61.2",
62
62
  ]
63
63
 
64
- [[package]]
65
- name = "anyhow"
66
- version = "1.0.102"
67
- source = "registry+https://github.com/rust-lang/crates.io-index"
68
- checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
69
-
70
64
  [[package]]
71
65
  name = "assert_cmd"
72
66
  version = "2.2.2"
@@ -124,7 +118,7 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
124
118
 
125
119
  [[package]]
126
120
  name = "bibsync"
127
- version = "0.3.5"
121
+ version = "0.4.0"
128
122
  dependencies = [
129
123
  "assert_cmd",
130
124
  "clap",
@@ -164,15 +158,15 @@ checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
164
158
 
165
159
  [[package]]
166
160
  name = "bytes"
167
- version = "1.11.1"
161
+ version = "1.12.0"
168
162
  source = "registry+https://github.com/rust-lang/crates.io-index"
169
- checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
163
+ checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593"
170
164
 
171
165
  [[package]]
172
166
  name = "cc"
173
- version = "1.2.63"
167
+ version = "1.2.65"
174
168
  source = "registry+https://github.com/rust-lang/crates.io-index"
175
- checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f"
169
+ checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96"
176
170
  dependencies = [
177
171
  "find-msvc-tools",
178
172
  "jobserver",
@@ -328,12 +322,6 @@ version = "1.0.5"
328
322
  source = "registry+https://github.com/rust-lang/crates.io-index"
329
323
  checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
330
324
 
331
- [[package]]
332
- name = "equivalent"
333
- version = "1.0.2"
334
- source = "registry+https://github.com/rust-lang/crates.io-index"
335
- checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
336
-
337
325
  [[package]]
338
326
  name = "errno"
339
327
  version = "0.3.14"
@@ -365,12 +353,6 @@ dependencies = [
365
353
  "num-traits",
366
354
  ]
367
355
 
368
- [[package]]
369
- name = "foldhash"
370
- version = "0.1.5"
371
- source = "registry+https://github.com/rust-lang/crates.io-index"
372
- checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
373
-
374
356
  [[package]]
375
357
  name = "form_urlencoded"
376
358
  version = "1.2.2"
@@ -464,32 +446,15 @@ dependencies = [
464
446
 
465
447
  [[package]]
466
448
  name = "getrandom"
467
- version = "0.4.2"
449
+ version = "0.4.3"
468
450
  source = "registry+https://github.com/rust-lang/crates.io-index"
469
- checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
451
+ checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
470
452
  dependencies = [
471
453
  "cfg-if",
472
454
  "libc",
473
455
  "r-efi 6.0.0",
474
- "wasip2",
475
- "wasip3",
476
456
  ]
477
457
 
478
- [[package]]
479
- name = "hashbrown"
480
- version = "0.15.5"
481
- source = "registry+https://github.com/rust-lang/crates.io-index"
482
- checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
483
- dependencies = [
484
- "foldhash",
485
- ]
486
-
487
- [[package]]
488
- name = "hashbrown"
489
- version = "0.17.1"
490
- source = "registry+https://github.com/rust-lang/crates.io-index"
491
- checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
492
-
493
458
  [[package]]
494
459
  name = "heck"
495
460
  version = "0.5.0"
@@ -674,12 +639,6 @@ dependencies = [
674
639
  "zerovec",
675
640
  ]
676
641
 
677
- [[package]]
678
- name = "id-arena"
679
- version = "2.3.0"
680
- source = "registry+https://github.com/rust-lang/crates.io-index"
681
- checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
682
-
683
642
  [[package]]
684
643
  name = "idna"
685
644
  version = "1.1.0"
@@ -701,18 +660,6 @@ dependencies = [
701
660
  "icu_properties",
702
661
  ]
703
662
 
704
- [[package]]
705
- name = "indexmap"
706
- version = "2.14.0"
707
- source = "registry+https://github.com/rust-lang/crates.io-index"
708
- checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
709
- dependencies = [
710
- "equivalent",
711
- "hashbrown 0.17.1",
712
- "serde",
713
- "serde_core",
714
- ]
715
-
716
663
  [[package]]
717
664
  name = "ipnet"
718
665
  version = "2.12.0"
@@ -792,21 +739,15 @@ dependencies = [
792
739
 
793
740
  [[package]]
794
741
  name = "js-sys"
795
- version = "0.3.100"
742
+ version = "0.3.102"
796
743
  source = "registry+https://github.com/rust-lang/crates.io-index"
797
- checksum = "f2025f20d7a4fa7785846e7b63d10a76d3f1cee98ee5cb79ea59703f95e42162"
744
+ checksum = "03d04c30968dffe80775bd4d7fb676131cd04a1fb46d2686dbffbaec2d9dfd31"
798
745
  dependencies = [
799
746
  "cfg-if",
800
747
  "futures-util",
801
748
  "wasm-bindgen",
802
749
  ]
803
750
 
804
- [[package]]
805
- name = "leb128fmt"
806
- version = "0.1.0"
807
- source = "registry+https://github.com/rust-lang/crates.io-index"
808
- checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
809
-
810
751
  [[package]]
811
752
  name = "libc"
812
753
  version = "0.2.186"
@@ -827,9 +768,9 @@ checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
827
768
 
828
769
  [[package]]
829
770
  name = "log"
830
- version = "0.4.32"
771
+ version = "0.4.33"
831
772
  source = "registry+https://github.com/rust-lang/crates.io-index"
832
- checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a"
773
+ checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
833
774
 
834
775
  [[package]]
835
776
  name = "lru-slab"
@@ -839,9 +780,9 @@ checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
839
780
 
840
781
  [[package]]
841
782
  name = "memchr"
842
- version = "2.8.1"
783
+ version = "2.8.2"
843
784
  source = "registry+https://github.com/rust-lang/crates.io-index"
844
- checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8"
785
+ checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
845
786
 
846
787
  [[package]]
847
788
  name = "mio"
@@ -953,16 +894,6 @@ dependencies = [
953
894
  "termtree",
954
895
  ]
955
896
 
956
- [[package]]
957
- name = "prettyplease"
958
- version = "0.2.37"
959
- source = "registry+https://github.com/rust-lang/crates.io-index"
960
- checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
961
- dependencies = [
962
- "proc-macro2",
963
- "syn",
964
- ]
965
-
966
897
  [[package]]
967
898
  name = "proc-macro2"
968
899
  version = "1.0.106"
@@ -974,9 +905,9 @@ dependencies = [
974
905
 
975
906
  [[package]]
976
907
  name = "pyo3"
977
- version = "0.28.3"
908
+ version = "0.29.0"
978
909
  source = "registry+https://github.com/rust-lang/crates.io-index"
979
- checksum = "91fd8e38a3b50ed1167fb981cd6fd60147e091784c427b8f7183a7ee32c31c12"
910
+ checksum = "cd274650b21d4bfc26a0a47587962c1edb425f69287324355cd040c3ea66071c"
980
911
  dependencies = [
981
912
  "libc",
982
913
  "once_cell",
@@ -988,18 +919,18 @@ dependencies = [
988
919
 
989
920
  [[package]]
990
921
  name = "pyo3-build-config"
991
- version = "0.28.3"
922
+ version = "0.29.0"
992
923
  source = "registry+https://github.com/rust-lang/crates.io-index"
993
- checksum = "e368e7ddfdeb98c9bca7f8383be1648fd84ab466bf2bc015e94008db6d35611e"
924
+ checksum = "c5e2a7d2f0d013342f295c048ad19237add5154a55b1c5a254c0ec93d4109078"
994
925
  dependencies = [
995
926
  "target-lexicon",
996
927
  ]
997
928
 
998
929
  [[package]]
999
930
  name = "pyo3-ffi"
1000
- version = "0.28.3"
931
+ version = "0.29.0"
1001
932
  source = "registry+https://github.com/rust-lang/crates.io-index"
1002
- checksum = "7f29e10af80b1f7ccaf7f69eace800a03ecd13e883acfacc1e5d0988605f651e"
933
+ checksum = "ca85c467da1bbc8d866eea5deff9cf29ea5f7785054a17da36e65bda9c05845b"
1003
934
  dependencies = [
1004
935
  "libc",
1005
936
  "pyo3-build-config",
@@ -1007,9 +938,9 @@ dependencies = [
1007
938
 
1008
939
  [[package]]
1009
940
  name = "pyo3-macros"
1010
- version = "0.28.3"
941
+ version = "0.29.0"
1011
942
  source = "registry+https://github.com/rust-lang/crates.io-index"
1012
- checksum = "df6e520eff47c45997d2fc7dd8214b25dd1310918bbb2642156ef66a67f29813"
943
+ checksum = "9ac53762fd065daa3194dd09337a38bd793a188100fd1a9304c4ab312d901771"
1013
944
  dependencies = [
1014
945
  "proc-macro2",
1015
946
  "pyo3-macros-backend",
@@ -1019,13 +950,12 @@ dependencies = [
1019
950
 
1020
951
  [[package]]
1021
952
  name = "pyo3-macros-backend"
1022
- version = "0.28.3"
953
+ version = "0.29.0"
1023
954
  source = "registry+https://github.com/rust-lang/crates.io-index"
1024
- checksum = "c4cdc218d835738f81c2338f822078af45b4afdf8b2e33cbb5916f108b813acb"
955
+ checksum = "4ca3a1557399783172dc5bf39cfca835157732532cba56b71d2292161e53b362"
1025
956
  dependencies = [
1026
957
  "heck",
1027
958
  "proc-macro2",
1028
- "pyo3-build-config",
1029
959
  "quote",
1030
960
  "syn",
1031
961
  ]
@@ -1088,9 +1018,9 @@ dependencies = [
1088
1018
 
1089
1019
  [[package]]
1090
1020
  name = "quote"
1091
- version = "1.0.45"
1021
+ version = "1.0.46"
1092
1022
  source = "registry+https://github.com/rust-lang/crates.io-index"
1093
- checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
1023
+ checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368"
1094
1024
  dependencies = [
1095
1025
  "proc-macro2",
1096
1026
  ]
@@ -1138,9 +1068,9 @@ dependencies = [
1138
1068
 
1139
1069
  [[package]]
1140
1070
  name = "regex"
1141
- version = "1.12.3"
1071
+ version = "1.12.4"
1142
1072
  source = "registry+https://github.com/rust-lang/crates.io-index"
1143
- checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
1073
+ checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba"
1144
1074
  dependencies = [
1145
1075
  "aho-corasick",
1146
1076
  "memchr",
@@ -1167,9 +1097,9 @@ checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973"
1167
1097
 
1168
1098
  [[package]]
1169
1099
  name = "regex-syntax"
1170
- version = "0.8.10"
1100
+ version = "0.8.11"
1171
1101
  source = "registry+https://github.com/rust-lang/crates.io-index"
1172
- checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
1102
+ checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
1173
1103
 
1174
1104
  [[package]]
1175
1105
  name = "reqwest"
@@ -1472,9 +1402,9 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
1472
1402
 
1473
1403
  [[package]]
1474
1404
  name = "smallvec"
1475
- version = "1.15.1"
1405
+ version = "1.15.2"
1476
1406
  source = "registry+https://github.com/rust-lang/crates.io-index"
1477
- checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
1407
+ checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
1478
1408
 
1479
1409
  [[package]]
1480
1410
  name = "socket2"
@@ -1506,9 +1436,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
1506
1436
 
1507
1437
  [[package]]
1508
1438
  name = "syn"
1509
- version = "2.0.117"
1439
+ version = "2.0.118"
1510
1440
  source = "registry+https://github.com/rust-lang/crates.io-index"
1511
- checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
1441
+ checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
1512
1442
  dependencies = [
1513
1443
  "proc-macro2",
1514
1444
  "quote",
@@ -1548,7 +1478,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1548
1478
  checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
1549
1479
  dependencies = [
1550
1480
  "fastrand",
1551
- "getrandom 0.4.2",
1481
+ "getrandom 0.4.3",
1552
1482
  "once_cell",
1553
1483
  "rustix",
1554
1484
  "windows-sys 0.61.2",
@@ -1715,12 +1645,6 @@ version = "1.0.24"
1715
1645
  source = "registry+https://github.com/rust-lang/crates.io-index"
1716
1646
  checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
1717
1647
 
1718
- [[package]]
1719
- name = "unicode-xid"
1720
- version = "0.2.6"
1721
- source = "registry+https://github.com/rust-lang/crates.io-index"
1722
- checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
1723
-
1724
1648
  [[package]]
1725
1649
  name = "untrusted"
1726
1650
  version = "0.9.0"
@@ -1791,23 +1715,14 @@ version = "1.0.1+wasi-0.2.4"
1791
1715
  source = "registry+https://github.com/rust-lang/crates.io-index"
1792
1716
  checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
1793
1717
  dependencies = [
1794
- "wit-bindgen 0.46.0",
1795
- ]
1796
-
1797
- [[package]]
1798
- name = "wasip3"
1799
- version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
1800
- source = "registry+https://github.com/rust-lang/crates.io-index"
1801
- checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
1802
- dependencies = [
1803
- "wit-bindgen 0.51.0",
1718
+ "wit-bindgen",
1804
1719
  ]
1805
1720
 
1806
1721
  [[package]]
1807
1722
  name = "wasm-bindgen"
1808
- version = "0.2.123"
1723
+ version = "0.2.125"
1809
1724
  source = "registry+https://github.com/rust-lang/crates.io-index"
1810
- checksum = "a254a4b10c19a76f09a27640e7ffbf9bc30bf67e16a3bf28aaefa4920fe81563"
1725
+ checksum = "8ddb3f79143bced6de84270411622a2699cee572fc0875aeaf1e7867cf9fca1a"
1811
1726
  dependencies = [
1812
1727
  "cfg-if",
1813
1728
  "once_cell",
@@ -1818,9 +1733,9 @@ dependencies = [
1818
1733
 
1819
1734
  [[package]]
1820
1735
  name = "wasm-bindgen-futures"
1821
- version = "0.4.73"
1736
+ version = "0.4.75"
1822
1737
  source = "registry+https://github.com/rust-lang/crates.io-index"
1823
- checksum = "54568702fabf5d4849ce2b90fadfa64168a097eaf4b351ce9df8b687a0086aaf"
1738
+ checksum = "503b14d284f2c8dac03b819967e155ea753f573586193b2b2c95990cb5d69280"
1824
1739
  dependencies = [
1825
1740
  "js-sys",
1826
1741
  "wasm-bindgen",
@@ -1828,9 +1743,9 @@ dependencies = [
1828
1743
 
1829
1744
  [[package]]
1830
1745
  name = "wasm-bindgen-macro"
1831
- version = "0.2.123"
1746
+ version = "0.2.125"
1832
1747
  source = "registry+https://github.com/rust-lang/crates.io-index"
1833
- checksum = "24a40fc75b0ec6f3746ceb10d36f53a93dcd68a93b11b6445983945d79eba0dc"
1748
+ checksum = "4e21a184b13fb19e157296e2c46056aec9092264fab83e4ba59e68c61b323c3d"
1834
1749
  dependencies = [
1835
1750
  "quote",
1836
1751
  "wasm-bindgen-macro-support",
@@ -1838,9 +1753,9 @@ dependencies = [
1838
1753
 
1839
1754
  [[package]]
1840
1755
  name = "wasm-bindgen-macro-support"
1841
- version = "0.2.123"
1756
+ version = "0.2.125"
1842
1757
  source = "registry+https://github.com/rust-lang/crates.io-index"
1843
- checksum = "908f34bd9b9ce3d4caf07b72dfab63d61504d156856c6bd3cd87fa350cf3985b"
1758
+ checksum = "fecefd9c35bd935a20fc3fc344b5f29138961e4f47fb03297d88f2587afb5ebd"
1844
1759
  dependencies = [
1845
1760
  "bumpalo",
1846
1761
  "proc-macro2",
@@ -1851,52 +1766,18 @@ dependencies = [
1851
1766
 
1852
1767
  [[package]]
1853
1768
  name = "wasm-bindgen-shared"
1854
- version = "0.2.123"
1769
+ version = "0.2.125"
1855
1770
  source = "registry+https://github.com/rust-lang/crates.io-index"
1856
- checksum = "7acbf7616c27b194bbb550bf77ed0c2c3e5b7fd1260a93082b95fb7f47959b92"
1771
+ checksum = "23939e44bb9a5d7576fa2b563dc2e136628f1224e88a8deed09e04858b77871f"
1857
1772
  dependencies = [
1858
1773
  "unicode-ident",
1859
1774
  ]
1860
1775
 
1861
- [[package]]
1862
- name = "wasm-encoder"
1863
- version = "0.244.0"
1864
- source = "registry+https://github.com/rust-lang/crates.io-index"
1865
- checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
1866
- dependencies = [
1867
- "leb128fmt",
1868
- "wasmparser",
1869
- ]
1870
-
1871
- [[package]]
1872
- name = "wasm-metadata"
1873
- version = "0.244.0"
1874
- source = "registry+https://github.com/rust-lang/crates.io-index"
1875
- checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
1876
- dependencies = [
1877
- "anyhow",
1878
- "indexmap",
1879
- "wasm-encoder",
1880
- "wasmparser",
1881
- ]
1882
-
1883
- [[package]]
1884
- name = "wasmparser"
1885
- version = "0.244.0"
1886
- source = "registry+https://github.com/rust-lang/crates.io-index"
1887
- checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
1888
- dependencies = [
1889
- "bitflags",
1890
- "hashbrown 0.15.5",
1891
- "indexmap",
1892
- "semver",
1893
- ]
1894
-
1895
1776
  [[package]]
1896
1777
  name = "web-sys"
1897
- version = "0.3.100"
1778
+ version = "0.3.102"
1898
1779
  source = "registry+https://github.com/rust-lang/crates.io-index"
1899
- checksum = "6e0871acf327f283dc6da28a1696cdc64fb355ba9f935d052021fa77f35cce69"
1780
+ checksum = "a6430a72df5eb332242960fe84b3002a241163998241eb596d4f739b9757061d"
1900
1781
  dependencies = [
1901
1782
  "js-sys",
1902
1783
  "wasm-bindgen",
@@ -1914,9 +1795,9 @@ dependencies = [
1914
1795
 
1915
1796
  [[package]]
1916
1797
  name = "webpki-root-certs"
1917
- version = "1.0.7"
1798
+ version = "1.0.8"
1918
1799
  source = "registry+https://github.com/rust-lang/crates.io-index"
1919
- checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c"
1800
+ checksum = "0d46a5a140e6f7afeccd8eae97eff335163939eac8b929834875168b29b3d267"
1920
1801
  dependencies = [
1921
1802
  "rustls-pki-types",
1922
1803
  ]
@@ -2098,94 +1979,6 @@ version = "0.46.0"
2098
1979
  source = "registry+https://github.com/rust-lang/crates.io-index"
2099
1980
  checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
2100
1981
 
2101
- [[package]]
2102
- name = "wit-bindgen"
2103
- version = "0.51.0"
2104
- source = "registry+https://github.com/rust-lang/crates.io-index"
2105
- checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
2106
- dependencies = [
2107
- "wit-bindgen-rust-macro",
2108
- ]
2109
-
2110
- [[package]]
2111
- name = "wit-bindgen-core"
2112
- version = "0.51.0"
2113
- source = "registry+https://github.com/rust-lang/crates.io-index"
2114
- checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
2115
- dependencies = [
2116
- "anyhow",
2117
- "heck",
2118
- "wit-parser",
2119
- ]
2120
-
2121
- [[package]]
2122
- name = "wit-bindgen-rust"
2123
- version = "0.51.0"
2124
- source = "registry+https://github.com/rust-lang/crates.io-index"
2125
- checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
2126
- dependencies = [
2127
- "anyhow",
2128
- "heck",
2129
- "indexmap",
2130
- "prettyplease",
2131
- "syn",
2132
- "wasm-metadata",
2133
- "wit-bindgen-core",
2134
- "wit-component",
2135
- ]
2136
-
2137
- [[package]]
2138
- name = "wit-bindgen-rust-macro"
2139
- version = "0.51.0"
2140
- source = "registry+https://github.com/rust-lang/crates.io-index"
2141
- checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
2142
- dependencies = [
2143
- "anyhow",
2144
- "prettyplease",
2145
- "proc-macro2",
2146
- "quote",
2147
- "syn",
2148
- "wit-bindgen-core",
2149
- "wit-bindgen-rust",
2150
- ]
2151
-
2152
- [[package]]
2153
- name = "wit-component"
2154
- version = "0.244.0"
2155
- source = "registry+https://github.com/rust-lang/crates.io-index"
2156
- checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
2157
- dependencies = [
2158
- "anyhow",
2159
- "bitflags",
2160
- "indexmap",
2161
- "log",
2162
- "serde",
2163
- "serde_derive",
2164
- "serde_json",
2165
- "wasm-encoder",
2166
- "wasm-metadata",
2167
- "wasmparser",
2168
- "wit-parser",
2169
- ]
2170
-
2171
- [[package]]
2172
- name = "wit-parser"
2173
- version = "0.244.0"
2174
- source = "registry+https://github.com/rust-lang/crates.io-index"
2175
- checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
2176
- dependencies = [
2177
- "anyhow",
2178
- "id-arena",
2179
- "indexmap",
2180
- "log",
2181
- "semver",
2182
- "serde",
2183
- "serde_derive",
2184
- "serde_json",
2185
- "unicode-xid",
2186
- "wasmparser",
2187
- ]
2188
-
2189
1982
  [[package]]
2190
1983
  name = "writeable"
2191
1984
  version = "0.6.3"
@@ -2217,18 +2010,18 @@ dependencies = [
2217
2010
 
2218
2011
  [[package]]
2219
2012
  name = "zerocopy"
2220
- version = "0.8.50"
2013
+ version = "0.8.52"
2221
2014
  source = "registry+https://github.com/rust-lang/crates.io-index"
2222
- checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1"
2015
+ checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f"
2223
2016
  dependencies = [
2224
2017
  "zerocopy-derive",
2225
2018
  ]
2226
2019
 
2227
2020
  [[package]]
2228
2021
  name = "zerocopy-derive"
2229
- version = "0.8.50"
2022
+ version = "0.8.52"
2230
2023
  source = "registry+https://github.com/rust-lang/crates.io-index"
2231
- checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639"
2024
+ checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930"
2232
2025
  dependencies = [
2233
2026
  "proc-macro2",
2234
2027
  "quote",
@@ -2258,9 +2051,9 @@ dependencies = [
2258
2051
 
2259
2052
  [[package]]
2260
2053
  name = "zeroize"
2261
- version = "1.8.2"
2054
+ version = "1.9.0"
2262
2055
  source = "registry+https://github.com/rust-lang/crates.io-index"
2263
- checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
2056
+ checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e"
2264
2057
 
2265
2058
  [[package]]
2266
2059
  name = "zerotrie"
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "bibsync"
3
- version = "0.3.5"
3
+ version = "0.4.0"
4
4
  edition = "2024"
5
5
  rust-version = "1.85"
6
6
  authors = ["Isaac C. F. Wong"]
@@ -16,7 +16,7 @@ exclude = ["/.github", "/docs/template_documentation"]
16
16
 
17
17
  [dependencies]
18
18
  clap = { version = "4.6.1", features = ["derive"] }
19
- pyo3 = { version = "0.28.3", optional = true, features = ["abi3-py312"] }
19
+ pyo3 = { version = "0.29.0", optional = true, features = ["abi3-py312"] }
20
20
  regex = "1.12.3"
21
21
  reqwest = { version = "0.13.4", default-features = false, features = [
22
22
  "blocking",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bibsync
3
- Version: 0.3.5
3
+ Version: 0.4.0
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Environment :: Console
6
6
  Classifier: Intended Audience :: Science/Research
@@ -65,6 +65,23 @@ NASA ADS and/or InspireHEP, rewrites provider BibTeX entries so the citekey stay
65
65
  the key used in TeX, and reports whether the output `.bib` file is current. With
66
66
  `--fix`, it writes the merged bibliography.
67
67
 
68
+ Markdown sources are supported too, including
69
+ [Journal of Open Source Software](https://joss.theoj.org/) `paper.md` files.
70
+ `bibsync` reads pandoc citations and the `bibliography` field from the YAML
71
+ frontmatter:
72
+
73
+ ```markdown
74
+ ---
75
+ bibliography: paper.bib
76
+ ---
77
+
78
+ Prior work [@1602.03837; @10.1103/PhysRevLett.116.061102].
79
+ ```
80
+
81
+ ```shell
82
+ bibsync --fix paper.md
83
+ ```
84
+
68
85
  When a citekey cannot be resolved, the command prints the key together with a
69
86
  reason and the likely fix. For example, an unsupported citekey is reported as an
70
87
  identifier-format problem, while an arXiv ID or DOI that the provider cannot find
@@ -117,7 +134,7 @@ For convenience, you can also use:
117
134
  @software{wong2026bibsync,
118
135
  author = {Wong, Isaac C. F.},
119
136
  title = {bibsync: A Rust package to automatically resolve, synchronize, and validate LaTeX citations across BibTeX databases},
120
- version = {v0.3.5},
137
+ version = {v0.4.0},
121
138
  year = {2026},
122
139
  month = jun,
123
140
  doi = {10.5281/zenodo.20422622},
@@ -269,7 +286,7 @@ Use the pre-built binary hook for faster installs:
269
286
  ```yaml
270
287
  repos:
271
288
  - repo: https://github.com/isaac-cf-wong/bibsync
272
- rev: v0.3.5
289
+ rev: v0.4.0
273
290
  hooks:
274
291
  - id: bibsync-bin
275
292
  args: [--cache, --provider, inspire, --output, references.bib]
@@ -284,7 +301,7 @@ installation:
284
301
  ```yaml
285
302
  repos:
286
303
  - repo: https://github.com/isaac-cf-wong/bibsync
287
- rev: v0.3.5
304
+ rev: v0.4.0
288
305
  hooks:
289
306
  - id: bibsync
290
307
  args: [--provider, inspire, --output, references.bib]
@@ -296,7 +313,7 @@ writing changes. To let the hook update files, add `--fix` to the hook args:
296
313
  ```yaml
297
314
  repos:
298
315
  - repo: https://github.com/isaac-cf-wong/bibsync
299
- rev: v0.3.5
316
+ rev: v0.4.0
300
317
  hooks:
301
318
  - id: bibsync-bin
302
319
  args: [--fix, --cache, --provider, inspire, --output, references.bib]
@@ -307,7 +324,7 @@ To skip manually curated entries, add `--ignore-file`:
307
324
  ```yaml
308
325
  repos:
309
326
  - repo: https://github.com/isaac-cf-wong/bibsync
310
- rev: v0.3.5
327
+ rev: v0.4.0
311
328
  hooks:
312
329
  - id: bibsync-bin
313
330
  args:
@@ -38,6 +38,23 @@ NASA ADS and/or InspireHEP, rewrites provider BibTeX entries so the citekey stay
38
38
  the key used in TeX, and reports whether the output `.bib` file is current. With
39
39
  `--fix`, it writes the merged bibliography.
40
40
 
41
+ Markdown sources are supported too, including
42
+ [Journal of Open Source Software](https://joss.theoj.org/) `paper.md` files.
43
+ `bibsync` reads pandoc citations and the `bibliography` field from the YAML
44
+ frontmatter:
45
+
46
+ ```markdown
47
+ ---
48
+ bibliography: paper.bib
49
+ ---
50
+
51
+ Prior work [@1602.03837; @10.1103/PhysRevLett.116.061102].
52
+ ```
53
+
54
+ ```shell
55
+ bibsync --fix paper.md
56
+ ```
57
+
41
58
  When a citekey cannot be resolved, the command prints the key together with a
42
59
  reason and the likely fix. For example, an unsupported citekey is reported as an
43
60
  identifier-format problem, while an arXiv ID or DOI that the provider cannot find
@@ -90,7 +107,7 @@ For convenience, you can also use:
90
107
  @software{wong2026bibsync,
91
108
  author = {Wong, Isaac C. F.},
92
109
  title = {bibsync: A Rust package to automatically resolve, synchronize, and validate LaTeX citations across BibTeX databases},
93
- version = {v0.3.5},
110
+ version = {v0.4.0},
94
111
  year = {2026},
95
112
  month = jun,
96
113
  doi = {10.5281/zenodo.20422622},
@@ -242,7 +259,7 @@ Use the pre-built binary hook for faster installs:
242
259
  ```yaml
243
260
  repos:
244
261
  - repo: https://github.com/isaac-cf-wong/bibsync
245
- rev: v0.3.5
262
+ rev: v0.4.0
246
263
  hooks:
247
264
  - id: bibsync-bin
248
265
  args: [--cache, --provider, inspire, --output, references.bib]
@@ -257,7 +274,7 @@ installation:
257
274
  ```yaml
258
275
  repos:
259
276
  - repo: https://github.com/isaac-cf-wong/bibsync
260
- rev: v0.3.5
277
+ rev: v0.4.0
261
278
  hooks:
262
279
  - id: bibsync
263
280
  args: [--provider, inspire, --output, references.bib]
@@ -269,7 +286,7 @@ writing changes. To let the hook update files, add `--fix` to the hook args:
269
286
  ```yaml
270
287
  repos:
271
288
  - repo: https://github.com/isaac-cf-wong/bibsync
272
- rev: v0.3.5
289
+ rev: v0.4.0
273
290
  hooks:
274
291
  - id: bibsync-bin
275
292
  args: [--fix, --cache, --provider, inspire, --output, references.bib]
@@ -280,7 +297,7 @@ To skip manually curated entries, add `--ignore-file`:
280
297
  ```yaml
281
298
  repos:
282
299
  - repo: https://github.com/isaac-cf-wong/bibsync
283
- rev: v0.3.5
300
+ rev: v0.4.0
284
301
  hooks:
285
302
  - id: bibsync-bin
286
303
  args:
@@ -1,7 +1,7 @@
1
1
  # Usage
2
2
 
3
- The main command accepts one or more TeX files, or a single BibTeX file in
4
- bibliography-refresh mode:
3
+ The main command accepts one or more TeX or Markdown files, or a single BibTeX
4
+ file in bibliography-refresh mode:
5
5
 
6
6
  ```shell
7
7
  bibsync [OPTIONS] [FILE]...
@@ -73,6 +73,36 @@ Use `--output` when you want explicit control:
73
73
  bibsync main.tex chapter.tex --output references.bib
74
74
  ```
75
75
 
76
+ ## Markdown And JOSS Papers
77
+
78
+ `bibsync` also scans Markdown sources, including
79
+ [Journal of Open Source Software](https://joss.theoj.org/) `paper.md` files. Files
80
+ ending in `.md` or `.markdown` are parsed for pandoc-style citations and for the
81
+ bibliography declared in the YAML frontmatter:
82
+
83
+ ```markdown
84
+ ---
85
+ title: "My Software"
86
+ bibliography: paper.bib
87
+ ---
88
+
89
+ Background and prior work [@1602.03837; @10.1103/PhysRevLett.116.061102].
90
+ ```
91
+
92
+ The `bibliography` frontmatter field is used as the output file when `--output`
93
+ is not given, matching the way `\bibliography{...}` is discovered in TeX. A single
94
+ value, an inline list (`[a.bib, b.bib]`), and a block list of `-` items are all
95
+ supported.
96
+
97
+ Pandoc citation keys (`@key`, `[@key]`, `[-@key]`, and grouped `[@a; @b]` forms)
98
+ are extracted from the body. Citations inside fenced or inline code spans are
99
+ ignored, and email addresses such as `name@example.com` are not mistaken for
100
+ citations. As with TeX, the most reliable workflow is to cite by identifier:
101
+
102
+ ```shell
103
+ bibsync --fix paper.md
104
+ ```
105
+
76
106
  ## Existing Bibliographies
77
107
 
78
108
  By default, `bibsync` leaves existing entries untouched unless they look like
@@ -10,7 +10,7 @@ use std::path::PathBuf;
10
10
  #[command(version, about)]
11
11
  #[allow(clippy::struct_excessive_bools)]
12
12
  struct Cli {
13
- /// TeX files to scan, or a single BibTeX file to check or update.
13
+ /// TeX or Markdown files to scan, or a single BibTeX file to check or update.
14
14
  #[arg(value_name = "FILE")]
15
15
  files: Vec<PathBuf>,
16
16
 
@@ -122,7 +122,7 @@ where
122
122
  return 0;
123
123
  }
124
124
  if cli.files.is_empty() {
125
- eprintln!("error: at least one TeX file or one BibTeX file is required");
125
+ eprintln!("error: at least one TeX file, Markdown file, or BibTeX file is required");
126
126
  return 2;
127
127
  }
128
128
 
@@ -525,10 +525,18 @@ fn scan_tex_files(files: &[PathBuf]) -> Result<TexScan> {
525
525
  let bib_re =
526
526
  Regex::new(r"\\(?:no)?bibliography\*?\s*\{([^{}]+)\}").expect("valid bibliography regex");
527
527
  let comment_re = Regex::new(r"(?m)(?P<prefix>^|[^\\])%.*$").expect("valid comment regex");
528
+ // Pandoc citation key: `@` preceded by start-of-text or a non-word, non-`@`
529
+ // character (so email addresses like `name@host` are not treated as cites).
530
+ let markdown_cite_re =
531
+ Regex::new(r"(?:^|[^\w@])@([\w][\w:./#$%&+?<>~-]*)").expect("valid markdown cite regex");
528
532
  let mut scan = TexScan::default();
529
533
 
530
534
  for file in files {
531
535
  let raw = read_to_string(file)?;
536
+ if is_markdown(file) {
537
+ scan_markdown_text(&raw, file, &markdown_cite_re, &mut scan);
538
+ continue;
539
+ }
532
540
  let text = comment_re.replace_all(&raw, "$prefix");
533
541
  for captures in cite_re.captures_iter(&text) {
534
542
  for key in captures[1]
@@ -545,18 +553,161 @@ fn scan_tex_files(files: &[PathBuf]) -> Result<TexScan> {
545
553
  .map(str::trim)
546
554
  .filter(|bib| !bib.is_empty())
547
555
  {
548
- let mut path = PathBuf::from(bib);
549
- if path.extension().is_none() {
550
- path.set_extension("bib");
556
+ scan.bibliographies
557
+ .push(resolve_bibliography_path(bib, file));
558
+ }
559
+ }
560
+ }
561
+ Ok(scan)
562
+ }
563
+
564
+ /// Scan a Markdown source (such as a JOSS `paper.md`) for pandoc citations and
565
+ /// the bibliography declared in the YAML frontmatter.
566
+ fn scan_markdown_text(raw: &str, file: &Path, cite_re: &Regex, scan: &mut TexScan) {
567
+ let (frontmatter, body) = split_markdown_frontmatter(raw);
568
+ if let Some(frontmatter) = frontmatter {
569
+ for spec in frontmatter_bibliographies(frontmatter) {
570
+ scan.bibliographies
571
+ .push(resolve_bibliography_path(&spec, file));
572
+ }
573
+ }
574
+ let body = strip_markdown_code(body);
575
+ for captures in cite_re.captures_iter(&body) {
576
+ let key = captures[1].trim_end_matches(['.', ',', ';', ':', '!', '?']);
577
+ if !key.is_empty() {
578
+ scan.citekeys.insert(key.to_owned());
579
+ }
580
+ }
581
+ }
582
+
583
+ fn is_markdown(path: &Path) -> bool {
584
+ has_extension(path, "md") || has_extension(path, "markdown")
585
+ }
586
+
587
+ /// Resolve a bibliography reference to a path, defaulting the extension to `.bib`
588
+ /// and resolving relative paths against the source file's directory.
589
+ fn resolve_bibliography_path(spec: &str, source: &Path) -> PathBuf {
590
+ let mut path = PathBuf::from(spec);
591
+ if path.extension().is_none() {
592
+ path.set_extension("bib");
593
+ }
594
+ if path.is_relative() {
595
+ path = source.parent().unwrap_or_else(|| Path::new(".")).join(path);
596
+ }
597
+ path
598
+ }
599
+
600
+ /// Split a Markdown document into its YAML frontmatter and body.
601
+ ///
602
+ /// Frontmatter is recognized only when the document opens with a `---` fence on
603
+ /// its first line, closed by a line containing exactly `---` or `...`.
604
+ fn split_markdown_frontmatter(text: &str) -> (Option<&str>, &str) {
605
+ let after_open = text
606
+ .strip_prefix("---\n")
607
+ .or_else(|| text.strip_prefix("---\r\n"));
608
+ let Some(after_open) = after_open else {
609
+ return (None, text);
610
+ };
611
+ let open_len = text.len() - after_open.len();
612
+ let mut offset = 0;
613
+ for line in after_open.split_inclusive('\n') {
614
+ let trimmed = line.trim_end_matches(['\n', '\r']);
615
+ if trimmed == "---" || trimmed == "..." {
616
+ let frontmatter = &after_open[..offset];
617
+ let body = &text[open_len + offset + line.len()..];
618
+ return (Some(frontmatter), body);
619
+ }
620
+ offset += line.len();
621
+ }
622
+ (None, text)
623
+ }
624
+
625
+ /// Extract `bibliography` entries from YAML frontmatter.
626
+ ///
627
+ /// Supports a single scalar (`bibliography: paper.bib`), an inline list
628
+ /// (`bibliography: [a.bib, b.bib]`), and a block list of `-` items.
629
+ fn frontmatter_bibliographies(frontmatter: &str) -> Vec<String> {
630
+ let mut bibliographies = Vec::new();
631
+ let mut lines = frontmatter.lines().peekable();
632
+ while let Some(line) = lines.next() {
633
+ // Only consider top-level keys so nested `bibliography` fields under
634
+ // other mappings are ignored.
635
+ if line.starts_with([' ', '\t']) {
636
+ continue;
637
+ }
638
+ let Some(value) = line.trim().strip_prefix("bibliography:") else {
639
+ continue;
640
+ };
641
+ let value = value.trim();
642
+ if value.is_empty() {
643
+ while let Some(next) = lines.peek() {
644
+ let trimmed = next.trim_start();
645
+ if let Some(item) = trimmed.strip_prefix('-') {
646
+ let item = clean_yaml_scalar(item);
647
+ if !item.is_empty() {
648
+ bibliographies.push(item);
649
+ }
650
+ lines.next();
651
+ } else if trimmed.is_empty() || trimmed.starts_with('#') {
652
+ // Skip blank lines and YAML comments so they do not
653
+ // truncate the bibliography list.
654
+ lines.next();
655
+ } else {
656
+ break;
551
657
  }
552
- if path.is_relative() {
553
- path = file.parent().unwrap_or_else(|| Path::new(".")).join(path);
658
+ }
659
+ } else if let Some(inner) = value.strip_prefix('[').and_then(|v| v.strip_suffix(']')) {
660
+ for item in inner.split(',') {
661
+ let item = clean_yaml_scalar(item);
662
+ if !item.is_empty() {
663
+ bibliographies.push(item);
554
664
  }
555
- scan.bibliographies.push(path);
665
+ }
666
+ } else {
667
+ let value = clean_yaml_scalar(value);
668
+ if !value.is_empty() {
669
+ bibliographies.push(value);
556
670
  }
557
671
  }
558
672
  }
559
- Ok(scan)
673
+ bibliographies
674
+ }
675
+
676
+ fn clean_yaml_scalar(value: &str) -> String {
677
+ let value = value.trim();
678
+ // Quoted scalars are taken verbatim; a `#` inside quotes is not a comment.
679
+ if let Some(rest) = value.strip_prefix('\'') {
680
+ if let Some(end) = rest.find('\'') {
681
+ return rest[..end].trim().to_owned();
682
+ }
683
+ }
684
+ if let Some(rest) = value.strip_prefix('"') {
685
+ if let Some(end) = rest.find('"') {
686
+ return rest[..end].trim().to_owned();
687
+ }
688
+ }
689
+ // Unquoted: strip a trailing YAML comment (a `#` preceded by whitespace or
690
+ // at the start of the value), leaving `#` that is part of the value alone.
691
+ strip_yaml_comment(value).trim().to_owned()
692
+ }
693
+
694
+ fn strip_yaml_comment(value: &str) -> &str {
695
+ let bytes = value.as_bytes();
696
+ for (index, &byte) in bytes.iter().enumerate() {
697
+ if byte == b'#' && (index == 0 || bytes[index - 1].is_ascii_whitespace()) {
698
+ return &value[..index];
699
+ }
700
+ }
701
+ value
702
+ }
703
+
704
+ /// Replace fenced and inline code spans with blanks so `@`-prefixed tokens in
705
+ /// code (for example Python decorators) are not mistaken for citations.
706
+ fn strip_markdown_code(text: &str) -> String {
707
+ let fenced = Regex::new(r"(?s)```.*?```|~~~.*?~~~").expect("valid fenced code regex");
708
+ let inline = Regex::new(r"`[^`\n]*`").expect("valid inline code regex");
709
+ let stripped = fenced.replace_all(text, " ");
710
+ inline.replace_all(&stripped, " ").into_owned()
560
711
  }
561
712
 
562
713
  #[derive(Clone, Debug, Eq, PartialEq)]
@@ -1611,7 +1762,7 @@ pub fn pre_commit_hook_manifest(ignore_file: Option<&Path>) -> String {
1611
1762
  "bibsync".to_owned()
1612
1763
  };
1613
1764
  format!(
1614
- "- id: bibsync\n name: bibsync\n description: Synchronize BibTeX entries from TeX citation keys\n entry: {entry}\n language: rust\n types_or: [tex, bib]\n"
1765
+ "- id: bibsync\n name: bibsync\n description: Synchronize BibTeX entries from TeX citation keys\n entry: {entry}\n language: rust\n types_or: [tex, markdown, bib]\n"
1615
1766
  )
1616
1767
  }
1617
1768
 
@@ -1681,6 +1832,68 @@ mod tests {
1681
1832
  assert!(!keys.contains("ignored"));
1682
1833
  }
1683
1834
 
1835
+ #[test]
1836
+ fn scans_joss_markdown_citations_and_bibliography() {
1837
+ let dir = tempdir().expect("tempdir");
1838
+ let paper = dir.path().join("paper.md");
1839
+ std::fs::write(
1840
+ &paper,
1841
+ "---\n\
1842
+ title: 'Example'\n\
1843
+ authors:\n\
1844
+ \x20\x20- name: A. Researcher\n\
1845
+ \x20\x20\x20\x20email: a@example.com\n\
1846
+ bibliography: paper.bib\n\
1847
+ ---\n\
1848
+ \n\
1849
+ Some prose citing [@2404.14498] and @arXiv:2312.00752.\n\
1850
+ A grouped cite [see @10.1234/example; @2404.14498].\n\
1851
+ \n\
1852
+ ```python\n\
1853
+ @decorator\n\
1854
+ def f():\n\
1855
+ \x20\x20\x20\x20pass\n\
1856
+ ```\n\
1857
+ Inline `@inline.code` is ignored too.\n",
1858
+ )
1859
+ .expect("write paper.md");
1860
+
1861
+ let scan = super::scan_tex_files(std::slice::from_ref(&paper)).expect("scan markdown");
1862
+ assert!(scan.citekeys.contains("2404.14498"));
1863
+ assert!(scan.citekeys.contains("arXiv:2312.00752"));
1864
+ assert!(scan.citekeys.contains("10.1234/example"));
1865
+ assert!(!scan.citekeys.contains("decorator"));
1866
+ assert!(!scan.citekeys.contains("inline.code"));
1867
+ assert!(!scan.citekeys.iter().any(|key| key.contains("example.com")));
1868
+ assert_eq!(scan.bibliographies, vec![dir.path().join("paper.bib")]);
1869
+ }
1870
+
1871
+ #[test]
1872
+ fn frontmatter_bibliography_supports_lists() {
1873
+ let inline = super::frontmatter_bibliographies("bibliography: [a.bib, 'b.bib']");
1874
+ assert_eq!(inline, vec!["a.bib".to_owned(), "b.bib".to_owned()]);
1875
+
1876
+ let block = super::frontmatter_bibliographies(
1877
+ "title: x\nbibliography:\n - a.bib\n - b.bib\nother: y",
1878
+ );
1879
+ assert_eq!(block, vec!["a.bib".to_owned(), "b.bib".to_owned()]);
1880
+ }
1881
+
1882
+ #[test]
1883
+ fn frontmatter_bibliography_strips_yaml_comments() {
1884
+ let scalar = super::frontmatter_bibliographies("bibliography: paper.bib # the refs");
1885
+ assert_eq!(scalar, vec!["paper.bib".to_owned()]);
1886
+
1887
+ let block = super::frontmatter_bibliographies(
1888
+ "bibliography:\n # primary\n - a.bib # main\n - b.bib\n",
1889
+ );
1890
+ assert_eq!(block, vec!["a.bib".to_owned(), "b.bib".to_owned()]);
1891
+
1892
+ // A `#` without preceding whitespace, or inside quotes, is part of the value.
1893
+ let hash = super::frontmatter_bibliographies("bibliography: 'paper #1.bib'");
1894
+ assert_eq!(hash, vec!["paper #1.bib".to_owned()]);
1895
+ }
1896
+
1684
1897
  #[test]
1685
1898
  fn syncs_arxiv_key_to_output_bib() {
1686
1899
  let dir = tempdir().expect("tempdir");
@@ -49,6 +49,33 @@ fn cli_defaults_to_check_mode() {
49
49
  ));
50
50
  }
51
51
 
52
+ #[test]
53
+ fn cli_scans_joss_markdown_paper() {
54
+ let dir = tempdir().expect("tempdir");
55
+ let paper = dir.path().join("paper.md");
56
+ let bib = dir.path().join("refs.bib");
57
+ std::fs::write(
58
+ &paper,
59
+ "---\ntitle: Example\nbibliography: refs.bib\n---\n\nText citing [@NotAnIdentifier].\n",
60
+ )
61
+ .expect("write paper.md");
62
+ std::fs::write(&bib, "").expect("write bib");
63
+
64
+ let mut command = Command::cargo_bin("bibsync").expect("binary exists");
65
+ command
66
+ .arg("--provider")
67
+ .arg("inspire")
68
+ .arg("--output")
69
+ .arg(&bib)
70
+ .arg(&paper)
71
+ .assert()
72
+ .failure()
73
+ .stdout(predicate::str::contains("unresolved:"))
74
+ .stdout(predicate::str::contains(
75
+ "NotAnIdentifier: unsupported identifier format",
76
+ ));
77
+ }
78
+
52
79
  #[test]
53
80
  fn cli_reports_missing_ignore_file() {
54
81
  let dir = tempdir().expect("tempdir");
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes