toolkit for mdBook [mirror of my GitHub repo] docs.tonywu.dev/mdbookkit/
permalinks rust-analyzer mdbook
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor(WIP): support mdbook 0.5

Tony Wu 4e63a0f3 4bfb84b5

+416 -979
+65 -250
Cargo.lock
··· 327 327 "anstyle", 328 328 "clap_lex", 329 329 "strsim", 330 - "terminal_size", 331 - ] 332 - 333 - [[package]] 334 - name = "clap_complete" 335 - version = "4.5.61" 336 - source = "registry+https://github.com/rust-lang/crates.io-index" 337 - checksum = "39615915e2ece2550c0149addac32fb5bd312c657f43845bb9088cb9c8a7c992" 338 - dependencies = [ 339 - "clap", 340 330 ] 341 331 342 332 [[package]] ··· 460 450 ] 461 451 462 452 [[package]] 463 - name = "darling" 464 - version = "0.20.10" 465 - source = "registry+https://github.com/rust-lang/crates.io-index" 466 - checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" 467 - dependencies = [ 468 - "darling_core", 469 - "darling_macro", 470 - ] 471 - 472 - [[package]] 473 - name = "darling_core" 474 - version = "0.20.10" 475 - source = "registry+https://github.com/rust-lang/crates.io-index" 476 - checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" 477 - dependencies = [ 478 - "fnv", 479 - "ident_case", 480 - "proc-macro2", 481 - "quote", 482 - "strsim", 483 - "syn 2.0.100", 484 - ] 485 - 486 - [[package]] 487 - name = "darling_macro" 488 - version = "0.20.10" 489 - source = "registry+https://github.com/rust-lang/crates.io-index" 490 - checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" 491 - dependencies = [ 492 - "darling_core", 493 - "quote", 494 - "syn 2.0.100", 495 - ] 496 - 497 - [[package]] 498 453 name = "derive_arbitrary" 499 454 version = "1.4.1" 500 455 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 502 457 dependencies = [ 503 458 "proc-macro2", 504 459 "quote", 505 - "syn 2.0.100", 506 - ] 507 - 508 - [[package]] 509 - name = "derive_builder" 510 - version = "0.20.2" 511 - source = "registry+https://github.com/rust-lang/crates.io-index" 512 - checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" 513 - dependencies = [ 514 - "derive_builder_macro", 515 - ] 516 - 517 - [[package]] 518 - name = "derive_builder_core" 519 - version = "0.20.2" 520 - source = "registry+https://github.com/rust-lang/crates.io-index" 521 - checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" 522 - dependencies = [ 523 - "darling", 524 - "proc-macro2", 525 - "quote", 526 - "syn 2.0.100", 527 - ] 528 - 529 - [[package]] 530 - name = "derive_builder_macro" 531 - version = "0.20.2" 532 - source = "registry+https://github.com/rust-lang/crates.io-index" 533 - checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" 534 - dependencies = [ 535 - "derive_builder_core", 536 460 "syn 2.0.100", 537 461 ] 538 462 ··· 851 775 ] 852 776 853 777 [[package]] 854 - name = "getopts" 855 - version = "0.2.21" 856 - source = "registry+https://github.com/rust-lang/crates.io-index" 857 - checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" 858 - dependencies = [ 859 - "unicode-width 0.1.14", 860 - ] 861 - 862 - [[package]] 863 778 name = "getrandom" 864 779 version = "0.1.16" 865 780 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 981 896 ] 982 897 983 898 [[package]] 984 - name = "handlebars" 985 - version = "6.3.2" 986 - source = "registry+https://github.com/rust-lang/crates.io-index" 987 - checksum = "759e2d5aea3287cb1190c8ec394f42866cb5bf74fcbf213f354e3c856ea26098" 988 - dependencies = [ 989 - "derive_builder", 990 - "log", 991 - "num-order", 992 - "pest", 993 - "pest_derive", 994 - "serde", 995 - "serde_json", 996 - "thiserror 2.0.12", 997 - ] 998 - 999 - [[package]] 1000 899 name = "hashbrown" 1001 900 version = "0.15.2" 1002 901 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1018 917 version = "0.5.0" 1019 918 source = "registry+https://github.com/rust-lang/crates.io-index" 1020 919 checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 1021 - 1022 - [[package]] 1023 - name = "hex" 1024 - version = "0.4.3" 1025 - source = "registry+https://github.com/rust-lang/crates.io-index" 1026 - checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 1027 920 1028 921 [[package]] 1029 922 name = "home" ··· 1288 1181 ] 1289 1182 1290 1183 [[package]] 1291 - name = "ident_case" 1292 - version = "1.0.1" 1293 - source = "registry+https://github.com/rust-lang/crates.io-index" 1294 - checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 1295 - 1296 - [[package]] 1297 1184 name = "idna" 1298 1185 version = "1.0.3" 1299 1186 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1555 1442 checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" 1556 1443 1557 1444 [[package]] 1558 - name = "mdbook" 1559 - version = "0.4.52" 1445 + name = "mdbook-core" 1446 + version = "0.5.1" 1560 1447 source = "registry+https://github.com/rust-lang/crates.io-index" 1561 - checksum = "93c284d2855916af7c5919cf9ad897cfc77d3c2db6f55429c7cfb769182030ec" 1448 + checksum = "2ef8430ec21b88489dfffd90c0fb9bd3eab96bf7642ef0cab74754b9d2e5b7f6" 1562 1449 dependencies = [ 1563 1450 "anyhow", 1564 - "chrono", 1565 - "clap", 1566 - "clap_complete", 1567 - "env_logger", 1568 - "handlebars", 1569 - "hex", 1570 - "log", 1571 - "memchr", 1572 - "opener", 1573 - "pulldown-cmark 0.10.3", 1574 1451 "regex", 1575 1452 "serde", 1576 1453 "serde_json", 1577 - "sha2", 1578 - "shlex", 1579 - "tempfile", 1580 - "toml 0.5.11", 1581 - "topological-sort", 1454 + "toml 0.9.8", 1455 + "tracing", 1456 + ] 1457 + 1458 + [[package]] 1459 + name = "mdbook-markdown" 1460 + version = "0.5.1" 1461 + source = "registry+https://github.com/rust-lang/crates.io-index" 1462 + checksum = "27f58c1b5686d0add2b513c15401177f84e87742ec381ccd4bfc2216de9a52e8" 1463 + dependencies = [ 1464 + "pulldown-cmark", 1465 + "regex", 1466 + "tracing", 1582 1467 ] 1583 1468 1584 1469 [[package]] ··· 1593 1478 "gix-url", 1594 1479 "insta", 1595 1480 "log", 1596 - "mdbook", 1481 + "mdbook-markdown", 1482 + "mdbook-preprocessor", 1597 1483 "mdbookkit", 1598 1484 "miette", 1599 1485 "percent-encoding", 1600 1486 "predicates", 1601 - "pulldown-cmark 0.13.0", 1602 1487 "rstest", 1603 1488 "serde", 1604 1489 "tap", ··· 1608 1493 ] 1609 1494 1610 1495 [[package]] 1496 + name = "mdbook-preprocessor" 1497 + version = "0.5.1" 1498 + source = "registry+https://github.com/rust-lang/crates.io-index" 1499 + checksum = "01f84f2b2ef3ccf2c6dd71255f9d90912cce7d5a5aa32899d033003d2c71f84d" 1500 + dependencies = [ 1501 + "anyhow", 1502 + "mdbook-core", 1503 + "serde", 1504 + "serde_json", 1505 + ] 1506 + 1507 + [[package]] 1611 1508 name = "mdbook-rustdoc-links" 1612 1509 version = "1.1.2" 1613 1510 dependencies = [ ··· 1621 1518 "insta", 1622 1519 "log", 1623 1520 "lsp-types", 1624 - "mdbook", 1521 + "mdbook-markdown", 1522 + "mdbook-preprocessor", 1625 1523 "mdbookkit", 1626 1524 "miette", 1627 1525 "predicates", 1628 1526 "proc-macro2", 1629 - "pulldown-cmark 0.13.0", 1630 1527 "rstest", 1631 1528 "serde", 1632 1529 "serde_json", ··· 1654 1551 "indicatif", 1655 1552 "insta", 1656 1553 "log", 1657 - "mdbook", 1554 + "mdbook-markdown", 1555 + "mdbook-preprocessor", 1658 1556 "miette", 1659 1557 "minijinja", 1660 1558 "owo-colors", 1661 - "pulldown-cmark 0.13.0", 1662 1559 "pulldown-cmark-to-cmark", 1663 1560 "serde", 1664 1561 "serde_json", ··· 1773 1670 checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" 1774 1671 1775 1672 [[package]] 1776 - name = "normpath" 1777 - version = "1.3.0" 1778 - source = "registry+https://github.com/rust-lang/crates.io-index" 1779 - checksum = "c8911957c4b1549ac0dc74e30db9c8b0e66ddcd6d7acc33098f4c63a64a6d7ed" 1780 - dependencies = [ 1781 - "windows-sys 0.59.0", 1782 - ] 1783 - 1784 - [[package]] 1785 - name = "num-modular" 1786 - version = "0.6.1" 1787 - source = "registry+https://github.com/rust-lang/crates.io-index" 1788 - checksum = "17bb261bf36fa7d83f4c294f834e91256769097b3cb505d44831e0a179ac647f" 1789 - 1790 - [[package]] 1791 - name = "num-order" 1792 - version = "1.2.0" 1793 - source = "registry+https://github.com/rust-lang/crates.io-index" 1794 - checksum = "537b596b97c40fcf8056d153049eb22f481c17ebce72a513ec9286e4986d1bb6" 1795 - dependencies = [ 1796 - "num-modular", 1797 - ] 1798 - 1799 - [[package]] 1800 1673 name = "num-traits" 1801 1674 version = "0.2.19" 1802 1675 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1827 1700 checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 1828 1701 1829 1702 [[package]] 1830 - name = "opener" 1831 - version = "0.8.3" 1832 - source = "registry+https://github.com/rust-lang/crates.io-index" 1833 - checksum = "cb9024962ab91e00c89d2a14352a8d0fc1a64346bf96f1839b45c09149564e47" 1834 - dependencies = [ 1835 - "bstr", 1836 - "normpath", 1837 - "windows-sys 0.60.2", 1838 - ] 1839 - 1840 - [[package]] 1841 1703 name = "openssl" 1842 1704 version = "0.10.71" 1843 1705 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1900 1762 checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 1901 1763 1902 1764 [[package]] 1903 - name = "pest" 1904 - version = "2.7.15" 1905 - source = "registry+https://github.com/rust-lang/crates.io-index" 1906 - checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" 1907 - dependencies = [ 1908 - "memchr", 1909 - "thiserror 2.0.12", 1910 - "ucd-trie", 1911 - ] 1912 - 1913 - [[package]] 1914 - name = "pest_derive" 1915 - version = "2.7.15" 1916 - source = "registry+https://github.com/rust-lang/crates.io-index" 1917 - checksum = "816518421cfc6887a0d62bf441b6ffb4536fcc926395a69e1a85852d4363f57e" 1918 - dependencies = [ 1919 - "pest", 1920 - "pest_generator", 1921 - ] 1922 - 1923 - [[package]] 1924 - name = "pest_generator" 1925 - version = "2.7.15" 1926 - source = "registry+https://github.com/rust-lang/crates.io-index" 1927 - checksum = "7d1396fd3a870fc7838768d171b4616d5c91f6cc25e377b673d714567d99377b" 1928 - dependencies = [ 1929 - "pest", 1930 - "pest_meta", 1931 - "proc-macro2", 1932 - "quote", 1933 - "syn 2.0.100", 1934 - ] 1935 - 1936 - [[package]] 1937 - name = "pest_meta" 1938 - version = "2.7.15" 1939 - source = "registry+https://github.com/rust-lang/crates.io-index" 1940 - checksum = "e1e58089ea25d717bfd31fb534e4f3afcc2cc569c70de3e239778991ea3b7dea" 1941 - dependencies = [ 1942 - "once_cell", 1943 - "pest", 1944 - "sha2", 1945 - ] 1946 - 1947 - [[package]] 1948 1765 name = "phf" 1949 1766 version = "0.8.0" 1950 1767 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2163 1980 2164 1981 [[package]] 2165 1982 name = "pulldown-cmark" 2166 - version = "0.10.3" 2167 - source = "registry+https://github.com/rust-lang/crates.io-index" 2168 - checksum = "76979bea66e7875e7509c4ec5300112b316af87fa7a252ca91c448b32dfe3993" 2169 - dependencies = [ 2170 - "bitflags 2.9.0", 2171 - "memchr", 2172 - "pulldown-cmark-escape 0.10.1", 2173 - "unicase", 2174 - ] 2175 - 2176 - [[package]] 2177 - name = "pulldown-cmark" 2178 1983 version = "0.13.0" 2179 1984 source = "registry+https://github.com/rust-lang/crates.io-index" 2180 1985 checksum = "1e8bbe1a966bd2f362681a44f6edce3c2310ac21e4d5067a6e7ec396297a6ea0" 2181 1986 dependencies = [ 2182 1987 "bitflags 2.9.0", 2183 - "getopts", 2184 1988 "memchr", 2185 - "pulldown-cmark-escape 0.11.0", 1989 + "pulldown-cmark-escape", 2186 1990 "unicase", 2187 1991 ] 2188 - 2189 - [[package]] 2190 - name = "pulldown-cmark-escape" 2191 - version = "0.10.1" 2192 - source = "registry+https://github.com/rust-lang/crates.io-index" 2193 - checksum = "bd348ff538bc9caeda7ee8cad2d1d48236a1f443c1fa3913c6a02fe0043b1dd3" 2194 1992 2195 1993 [[package]] 2196 1994 name = "pulldown-cmark-escape" ··· 2204 2002 source = "registry+https://github.com/rust-lang/crates.io-index" 2205 2003 checksum = "8246feae3db61428fd0bb94285c690b460e4517d83152377543ca802357785f1" 2206 2004 dependencies = [ 2207 - "pulldown-cmark 0.13.0", 2005 + "pulldown-cmark", 2208 2006 ] 2209 2007 2210 2008 [[package]] ··· 2649 2447 ] 2650 2448 2651 2449 [[package]] 2450 + name = "serde_spanned" 2451 + version = "1.0.3" 2452 + source = "registry+https://github.com/rust-lang/crates.io-index" 2453 + checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" 2454 + dependencies = [ 2455 + "serde_core", 2456 + ] 2457 + 2458 + [[package]] 2652 2459 name = "serde_urlencoded" 2653 2460 version = "0.7.1" 2654 2461 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3014 2821 checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" 3015 2822 dependencies = [ 3016 2823 "serde", 3017 - "serde_spanned", 2824 + "serde_spanned 0.6.8", 3018 2825 "toml_datetime 0.6.8", 3019 2826 "toml_edit 0.22.24", 3020 2827 ] 3021 2828 3022 2829 [[package]] 2830 + name = "toml" 2831 + version = "0.9.8" 2832 + source = "registry+https://github.com/rust-lang/crates.io-index" 2833 + checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" 2834 + dependencies = [ 2835 + "indexmap", 2836 + "serde_core", 2837 + "serde_spanned 1.0.3", 2838 + "toml_datetime 0.7.3", 2839 + "toml_parser", 2840 + "toml_writer", 2841 + "winnow 0.7.14", 2842 + ] 2843 + 2844 + [[package]] 3023 2845 name = "toml_datetime" 3024 2846 version = "0.6.8" 3025 2847 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3056 2878 dependencies = [ 3057 2879 "indexmap", 3058 2880 "serde", 3059 - "serde_spanned", 2881 + "serde_spanned 0.6.8", 3060 2882 "toml_datetime 0.6.8", 3061 2883 "winnow 0.7.14", 3062 2884 ] ··· 3083 2905 ] 3084 2906 3085 2907 [[package]] 3086 - name = "topological-sort" 3087 - version = "0.2.2" 2908 + name = "toml_writer" 2909 + version = "1.0.4" 3088 2910 source = "registry+https://github.com/rust-lang/crates.io-index" 3089 - checksum = "ea68304e134ecd095ac6c3574494fc62b909f416c4fca77e440530221e549d3d" 2911 + checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" 3090 2912 3091 2913 [[package]] 3092 2914 name = "tower" ··· 3157 2979 version = "1.18.0" 3158 2980 source = "registry+https://github.com/rust-lang/crates.io-index" 3159 2981 checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" 3160 - 3161 - [[package]] 3162 - name = "ucd-trie" 3163 - version = "0.1.7" 3164 - source = "registry+https://github.com/rust-lang/crates.io-index" 3165 - checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" 3166 2982 3167 2983 [[package]] 3168 2984 name = "unicase" ··· 3256 3072 "clap", 3257 3073 "flate2", 3258 3074 "indicatif", 3259 - "mdbook", 3260 3075 "reqwest", 3261 3076 "serde_json", 3262 3077 "tap",
+4 -4
Cargo.toml
··· 26 26 env_logger = "0.11.6" 27 27 insta = { version = "1.40.0", features = ["yaml", "filters"] } 28 28 log = "0.4.26" 29 - mdbook = { version = "0.4.48", default-features = false } 29 + mdbook-markdown = { version = "0.5.1" } 30 + mdbook-preprocessor = { version = "0.5.1" } 30 31 mdbookkit = { path = "crates/mdbookkit" } 31 32 miette = { version = "7.5.0", features = [ 32 33 "fancy-no-backtrace", 33 34 ], default-features = false } 34 35 minijinja = "2.9.0" 35 36 predicates = "3.1.3" 36 - pulldown-cmark = "0.13.0" 37 - pulldown-cmark-to-cmark = "21.0.0" 37 + pulldown-cmark-to-cmark = "21.1.0" 38 38 rstest = "0.26.1" 39 39 serde = { version = "1", features = ["derive"] } 40 40 serde_json = "1.0.139" ··· 48 48 url = "2.5.4" 49 49 50 50 [workspace.metadata.bin] 51 - mdbook = { version = "0.4.48" } 51 + mdbook = { version = "0.5.1" } 52 52 mdbook-alerts = { version = "0.7.0" }
+2 -2
crates/mdbook-permalinks/Cargo.toml
··· 23 23 git2 = { version = "0.20.1", default-features = false } 24 24 gix-url = { version = "0.30.0" } 25 25 log = { workspace = true } 26 - mdbook = { workspace = true } 26 + mdbook-markdown = { workspace = true } 27 + mdbook-preprocessor = { workspace = true } 27 28 mdbookkit = { workspace = true } 28 29 miette = { workspace = true } 29 30 percent-encoding = { version = "2.3.1" } 30 - pulldown-cmark = { workspace = true } 31 31 serde = { workspace = true } 32 32 tap = { workspace = true } 33 33 thiserror = { workspace = true }
-10
crates/mdbook-permalinks/src/diagnostic.rs
··· 84 84 LinkStatus::Rewritten => Some(format!("file: {link}\nlink: {}", self.link.link)), 85 85 LinkStatus::PathNotCheckedIn => Some(format!("{status}: {link}")), 86 86 LinkStatus::NoSuchPath => Some(format!("{status}: {link}")), 87 - LinkStatus::NoSuchFragment => { 88 - let (_, fragment) = self 89 - .link 90 - .link 91 - .split_once('#') 92 - .expect("should have a fragment"); 93 - Some(format!("#{fragment} not found in {link}")) 94 - } 95 87 LinkStatus::Error(..) => Some(format!("{status}")), 96 88 }; 97 89 LabeledSpan::new_with_span(label, self.link.span.clone()) ··· 128 120 Self::Permalink => log::Level::Info, 129 121 Self::PathNotCheckedIn => log::Level::Warn, 130 122 Self::NoSuchPath => log::Level::Warn, 131 - Self::NoSuchFragment => log::Level::Warn, 132 123 Self::Error(..) => log::Level::Warn, 133 124 } 134 125 } ··· 158 149 fn order(&self) -> usize { 159 150 match self { 160 151 Self::Error(..) => 103, 161 - Self::NoSuchFragment => 102, 162 152 Self::NoSuchPath => 101, 163 153 Self::PathNotCheckedIn => 100, 164 154 Self::Permalink => 3,
+1 -3
crates/mdbook-permalinks/src/link.rs
··· 1 1 use std::{fmt::Debug, ops::Range}; 2 2 3 - use pulldown_cmark::{CowStr, Event, LinkType, Tag, TagEnd}; 3 + use mdbook_markdown::pulldown_cmark::{CowStr, Event, LinkType, Tag, TagEnd}; 4 4 5 5 #[derive(Debug, Default, Clone, thiserror::Error)] 6 6 pub enum LinkStatus { ··· 21 21 PathNotCheckedIn, 22 22 #[error("file does not exist at path")] 23 23 NoSuchPath, 24 - #[error("fragment does not exist in page")] 25 - NoSuchFragment, 26 24 27 25 #[error("error generating a link: {0}")] 28 26 Error(String),
+117 -159
crates/mdbook-permalinks/src/main.rs
··· 3 3 use anyhow::{Context, Result, anyhow}; 4 4 use console::colors_enabled_stderr; 5 5 use log::LevelFilter; 6 - use mdbook::preprocess::PreprocessorContext; 7 - use percent_encoding::percent_decode_str; 6 + use mdbook_markdown::pulldown_cmark; 7 + use mdbook_preprocessor::{Preprocessor, PreprocessorContext, book::Book}; 8 8 use serde::Deserialize; 9 - use tap::{Pipe, Tap, TapFallible}; 9 + use tap::{Pipe, TapFallible}; 10 10 use url::Url; 11 11 12 12 use mdbookkit::{ 13 - book::{ 14 - book_from_stdin, book_into_stdout, config_from_book, for_each_chapter_mut, iter_chapters, 15 - smart_punctuation, 16 - }, 13 + book::{BookConfigHelper, BookHelper, book_from_stdin}, 17 14 diagnostics::Issue, 18 15 error::OnWarning, 19 16 log_debug, log_warning, 20 17 logging::{ConsoleLogger, is_logging}, 21 - markdown::mdbook_markdown_options, 22 18 }; 23 19 24 20 use self::{ 25 21 link::{LinkStatus, RelativeLink}, 26 - page::{Fragments, Pages}, 22 + page::Pages, 27 23 vcs::{Permalink, PermalinkFormat}, 28 24 }; 29 25 ··· 34 30 mod tests; 35 31 mod vcs; 36 32 37 - fn main() -> Result<()> { 38 - ConsoleLogger::install(env!("CARGO_PKG_NAME")); 33 + struct Permalinks; 39 34 40 - let Program { command } = clap::Parser::parse(); 35 + impl Preprocessor for Permalinks { 36 + fn name(&self) -> &str { 37 + env!("CARGO_PKG_NAME") 38 + } 41 39 42 - match command { 43 - Some(Command::Supports { .. }) => return Ok(()), 44 - #[cfg(feature = "_testing")] 45 - Some(Command::Describe) => { 46 - print!("{}", mdbookkit::docs::describe_preprocessor::<Config>()?); 47 - return Ok(()); 40 + fn run(&self, ctx: &PreprocessorContext, book: Book) -> Result<Book> { 41 + match Environment::new(ctx) { 42 + Ok(Ok(env)) => env.run(ctx, book), 43 + Ok(Err(err)) => { 44 + log::warn!("{:?}", err.context("preprocessor will be disabled")); 45 + Ok(book) 46 + } 47 + Err(err) => Err(err).context(format!( 48 + "failed to initialize preprocessor `{}`", 49 + self.name() 50 + )), 48 51 } 49 - None => {} 50 52 } 51 - 52 - let (context, mut book) = book_from_stdin().context("failed to parse book content")?; 53 + } 53 54 54 - let env = match Environment::new(&context) { 55 - Ok(Ok(env)) => env, 56 - Ok(Err(err)) => { 57 - log::warn!("{:?}", err.context("preprocessor will be disabled")); 58 - return book_into_stdout(&book); 59 - } 60 - Err(err) => { 61 - return Err(err).context(concat!( 62 - "failed to initialize preprocessor `", 63 - env!("CARGO_PKG_NAME"), 64 - "`" 65 - )); 66 - } 67 - }; 55 + struct Environment { 56 + book_src: Url, 57 + markdown: pulldown_cmark::Options, 58 + vcs: VersionControl, 59 + config: Config, 60 + } 68 61 69 - let mut content = Pages::new(env.markdown); 62 + struct VersionControl { 63 + root: Url, 64 + link: Permalink, 65 + } 70 66 71 - for (path, ch) in iter_chapters(&book) { 72 - let url = env 73 - .book_src 74 - .join(&path.to_string_lossy()) 75 - .context("could not read path as a url")?; 76 - content 77 - .insert(url, &ch.content) 78 - .with_context(|| path.display().to_string()) 79 - .context("failed to parse Markdown source:")?; 67 + impl Preprocessor for Environment { 68 + fn name(&self) -> &str { 69 + PREPROCESSOR_NAME 80 70 } 81 71 82 - env.resolve(&mut content); 72 + fn run(&self, _: &PreprocessorContext, mut book: Book) -> Result<Book> { 73 + let mut content = Pages::new(self.markdown); 83 74 84 - let mut result = iter_chapters(&book) 85 - .filter_map(|(path, _)| { 86 - let url = env.book_src.join(&path.to_string_lossy()).unwrap(); 75 + for (path, ch) in book.iter_chapters() { 76 + let url = self 77 + .book_src 78 + .join(&path.to_string_lossy()) 79 + .context("could not read path as a url")?; 87 80 content 88 - .emit(&url) 89 - .tap_err(log_warning!()) 90 - .ok() 91 - .map(|output| (path.clone(), output.to_string())) 92 - }) 93 - .collect::<HashMap<_, _>>(); 94 - 95 - let status = env 96 - .report_issues(&content, |_| true) 97 - .names(|url| env.rel_path(url)) 98 - .level(LevelFilter::Warn) 99 - .logging(is_logging()) 100 - .colored(colors_enabled_stderr()) 101 - .build() 102 - .to_stderr() 103 - .to_status(); 104 - 105 - for_each_chapter_mut(&mut book, |path, ch| { 106 - if let Some(output) = result.remove(&path) { 107 - ch.content = output 81 + .insert(url, &ch.content) 82 + .with_context(|| path.display().to_string()) 83 + .context("failed to parse Markdown source:")?; 108 84 } 109 - }); 110 - 111 - book_into_stdout(&book)?; 112 85 113 - env.config.fail_on_warnings.check(status.level())?; 86 + self.resolve(&mut content); 114 87 115 - Ok(()) 116 - } 88 + let mut result = book 89 + .iter_chapters() 90 + .filter_map(|(path, _)| { 91 + let url = self.book_src.join(&path.to_string_lossy()).unwrap(); 92 + content 93 + .emit(&url) 94 + .tap_err(log_warning!()) 95 + .ok() 96 + .map(|output| (path.clone(), output.to_string())) 97 + }) 98 + .collect::<HashMap<_, _>>(); 117 99 118 - #[derive(clap::Parser, Debug, Clone)] 119 - struct Program { 120 - #[command(subcommand)] 121 - command: Option<Command>, 122 - } 100 + let status = self 101 + .report_issues(&content, |_| true) 102 + .names(|url| self.rel_path(url)) 103 + .level(LevelFilter::Warn) 104 + .logging(is_logging()) 105 + .colored(colors_enabled_stderr()) 106 + .build() 107 + .to_stderr() 108 + .to_status(); 123 109 124 - #[derive(clap::Subcommand, Debug, Clone)] 125 - enum Command { 126 - #[clap(hide = true)] 127 - Supports { renderer: String }, 128 - #[cfg(feature = "_testing")] 129 - #[clap(hide = true)] 130 - Describe, 131 - } 110 + book.for_each_chapter_mut(|ch| { 111 + if let Some(path) = &ch.source_path 112 + && let Some(output) = result.remove(path) 113 + { 114 + ch.content = output 115 + } 116 + }); 132 117 133 - struct Environment { 134 - book_src: Url, 135 - markdown: pulldown_cmark::Options, 136 - vcs: VersionControl, 137 - config: Config, 138 - } 118 + self.config.fail_on_warnings.check(status.level())?; 139 119 140 - struct VersionControl { 141 - root: Url, 142 - link: Permalink, 120 + Ok(book) 121 + } 143 122 } 144 123 145 124 impl Environment { 146 125 fn resolve(&self, content: &mut Pages<'_>) { 147 126 self.validate(); 148 127 149 - let fragments = content.take_fragments(); 150 - 151 128 for (base, link) in content.links_mut() { 152 129 let file = if link.link.starts_with('/') { 153 130 self.vcs.root.join(&link.link[1..]) ··· 164 141 165 142 let env = self; 166 143 let page_url = base.as_ref(); 167 - let fragments = &fragments; 168 144 169 145 Resolver { 170 146 link, 171 147 page_url, 172 148 file_url, 173 149 env, 174 - fragments, 175 150 } 176 151 .resolve(); 177 152 } 178 - 179 - let mut fragments = fragments; 180 - fragments.restore(content); 181 - drop(fragments); 182 153 } 183 154 184 155 #[inline] ··· 196 167 } 197 168 198 169 fn new(book: &PreprocessorContext) -> Result<Result<Self>> { 199 - let config = config_from_book::<Config>(&book.config, env!("CARGO_PKG_NAME")) 170 + let config = book 171 + .config 172 + .preprocessor(PREPROCESSOR_NAME) 200 173 .context("failed to read preprocessor config from book.toml")?; 201 174 202 175 let vcs = match VersionControl::try_from_git(&config, &book.config) { ··· 205 178 Err(err) => return Err(err), 206 179 }; 207 180 208 - let markdown = mdbook_markdown_options().tap_mut(|m| { 209 - if smart_punctuation(&book.config) { 210 - m.insert(pulldown_cmark::Options::ENABLE_SMART_PUNCTUATION); 211 - } 212 - }); 181 + let markdown = book.config.markdown_options(); 213 182 214 183 let book_src = book 215 184 .root ··· 234 203 page_url: &'a Url, 235 204 link: &'a mut RelativeLink<'r>, 236 205 env: &'a Environment, 237 - fragments: &'a Fragments, 238 206 } 239 207 240 208 impl Resolver<'_, '_> { ··· 257 225 page_url, 258 226 file_url, 259 227 env, 260 - fragments, 228 + .. 261 229 } = self; 262 230 263 231 let file_url = if let Some(path) = env.vcs.link.to_path(&file_url) { ··· 327 295 } else { 328 296 link.status = LinkStatus::Published; 329 297 } 330 - Self { 331 - link, 332 - page_url, 333 - file_url, 334 - env, 335 - fragments, 336 - } 337 - .resolve_fragment(); 338 298 return; 339 299 } 340 300 ··· 353 313 file_url, 354 314 page_url, 355 315 link, 356 - env, 357 - fragments, 316 + .. 358 317 } = self; 359 318 360 319 let path = { ··· 421 380 link.link = page_url.make_relative(&file_url).unwrap().into(); 422 381 link.status = LinkStatus::Rewritten; 423 382 424 - Self { 425 - link, 426 - page_url, 427 - file_url, 428 - env, 429 - fragments, 430 - } 431 - .resolve_fragment(); 432 - 433 383 return; 434 384 } 435 385 ··· 438 388 439 389 link.link = not_found[0].to_string().into(); 440 390 link.status = LinkStatus::NoSuchPath; 441 - } 442 - 443 - fn resolve_fragment(self) { 444 - let Self { 445 - mut file_url, 446 - link, 447 - fragments, 448 - .. 449 - } = self; 450 - 451 - let Some(fragment) = file_url 452 - .fragment() 453 - .and_then(|f| percent_decode_str(f).decode_utf8().ok().or(Some(f.into()))) 454 - .map(|f| f.into_owned()) 455 - else { 456 - return; 457 - }; 458 - 459 - file_url.set_fragment(None); 460 - 461 - let found = fragments.contains(&file_url, &fragment); 462 - 463 - file_url.set_fragment(Some(&fragment)); 464 - 465 - if !found { 466 - link.status = LinkStatus::NoSuchFragment; 467 - } 468 391 } 469 392 } 470 393 ··· 592 515 Ok(Self::from(url)) 593 516 } 594 517 } 518 + 519 + fn main() -> Result<()> { 520 + ConsoleLogger::install(env!("CARGO_PKG_NAME")); 521 + let Program { command } = clap::Parser::parse(); 522 + match command { 523 + None => { 524 + let (ctx, book) = book_from_stdin().context("failed to parse book content")?; 525 + Permalinks.run(&ctx, book)?.to_stdout()?; 526 + Ok(()) 527 + } 528 + Some(Command::Supports { .. }) => Ok(()), 529 + #[cfg(feature = "_testing")] 530 + Some(Command::Describe) => { 531 + print!("{}", mdbookkit::docs::describe_preprocessor::<Config>()?); 532 + Ok(()) 533 + } 534 + } 535 + } 536 + 537 + #[derive(clap::Parser, Debug, Clone)] 538 + struct Program { 539 + #[command(subcommand)] 540 + command: Option<Command>, 541 + } 542 + 543 + #[derive(clap::Subcommand, Debug, Clone)] 544 + enum Command { 545 + #[clap(hide = true)] 546 + Supports { renderer: String }, 547 + #[cfg(feature = "_testing")] 548 + #[clap(hide = true)] 549 + Describe, 550 + } 551 + 552 + static PREPROCESSOR_NAME: &str = env!("CARGO_PKG_NAME");
+7 -103
crates/mdbook-permalinks/src/page.rs
··· 1 - use std::{ 2 - borrow::Borrow, 3 - collections::{HashMap, HashSet}, 4 - fmt::Debug, 5 - hash::Hash, 6 - ops::Range, 7 - sync::Arc, 8 - }; 1 + use std::{borrow::Borrow, collections::HashMap, fmt::Debug, hash::Hash, sync::Arc}; 9 2 10 3 use anyhow::{Context, Result, bail}; 11 - use mdbook::utils::unique_id_from_content; 12 - use pulldown_cmark::{Event, Options, Parser, Tag, TagEnd, html::push_html}; 13 - use tap::{Pipe, Tap, TapFallible}; 4 + use mdbook_markdown::pulldown_cmark::{Event, Options, Parser, Tag, TagEnd}; 5 + use tap::{Pipe, TapFallible}; 14 6 use url::Url; 15 7 16 8 use mdbookkit::{ ··· 28 20 struct Page<'a> { 29 21 source: &'a str, 30 22 links: Vec<LinkSpan<'a>>, 31 - fragments: HashSet<String>, 32 23 } 33 24 34 25 impl<'a> Pages<'a> { ··· 75 66 let page = page.with_context(|| format!("no such document {key:?}"))?; 76 67 page.emit() 77 68 } 78 - 79 - pub fn take_fragments(&mut self) -> Fragments { 80 - self.pages 81 - .iter_mut() 82 - .map(|(url, page)| (url.clone(), std::mem::take(&mut page.fragments))) 83 - .collect::<HashMap<_, _>>() 84 - .pipe(Fragments) 85 - } 86 69 } 87 70 88 71 impl<'a> Page<'a> { ··· 93 76 let mut this = Self { 94 77 source, 95 78 links: Default::default(), 96 - fragments: Default::default(), 97 79 }; 98 80 99 - struct Heading<'a> { 100 - span: Range<usize>, 101 - text: Vec<Event<'a>>, 102 - } 103 - 104 - let mut heading: Option<Heading<'_>> = None; 105 - let mut counter: HashMap<String, usize> = Default::default(); 106 - 107 81 let mut opened: Option<LinkSpan<'_>> = None; 108 82 109 83 for (event, span) in stream { 110 84 match event { 111 - Event::Start(Tag::Heading { id, .. }) => { 112 - if let Some(id) = id { 113 - this.insert_id(&id, &mut counter); 114 - } else if heading.is_some() { 115 - bail!("unexpected `Tag::Heading` in `Tag::Heading` at {span:?}"); 116 - } else { 117 - heading = Some(Heading { span, text: vec![] }) 118 - } 119 - } 120 - 121 - Event::End(TagEnd::Heading(..)) => { 122 - let Some(Heading { span: start, text }) = heading.take() else { 123 - bail!("unexpected `TagEnd::Heading` at {span:?}") 124 - }; 125 - if start != span { 126 - bail!("mismatching span, expected {start:?}, got {span:?}") 127 - } 128 - this.slugify(text.iter(), &mut counter); 129 - } 130 - 131 85 Event::Start(tag @ (Tag::Link { .. } | Tag::Image { .. })) => { 132 86 let (usage, link, title) = match tag { 133 87 Tag::Link { ··· 169 123 } 170 124 } 171 125 172 - event => match (heading.as_mut(), opened.as_mut()) { 173 - (Some(heading), Some(link)) => { 174 - heading.text.push(event.clone()); 175 - link.0.push(LinkText::Text(event)); 126 + event => { 127 + if let Some(link) = opened.as_mut() { 128 + link.0.push(LinkText::Text(event)) 176 129 } 177 - (Some(heading), None) => { 178 - heading.text.push(event); 179 - } 180 - (None, Some(link)) => { 181 - link.0.push(LinkText::Text(event)); 182 - } 183 - (None, None) => {} 184 - }, 130 + } 185 131 } 186 132 } 187 133 ··· 196 142 .into_string() 197 143 .tap_err(log_warning!())? 198 144 .pipe(Ok) 199 - } 200 - 201 - fn slugify<'r, S>(&mut self, heading: S, counter: &mut HashMap<String, usize>) 202 - where 203 - S: Iterator<Item = &'r Event<'r>>, 204 - { 205 - let fragment = String::new().tap_mut(|s| push_html(s, heading.cloned())); 206 - let fragment = unique_id_from_content(&fragment, counter); 207 - self.fragments.insert(fragment); 208 - } 209 - 210 - fn insert_id(&mut self, id: &str, counter: &mut HashMap<String, usize>) { 211 - counter.insert(id.into(), 1); 212 - self.fragments.insert(id.into()); 213 - } 214 - } 215 - 216 - #[must_use] 217 - pub struct Fragments(HashMap<Arc<Url>, HashSet<String>>); 218 - 219 - impl Fragments { 220 - pub fn contains(&self, page: &Url, fragment: &str) -> bool { 221 - self.0 222 - .get(page) 223 - .map(|f| f.contains(fragment)) 224 - .unwrap_or(false) 225 - } 226 - 227 - pub fn restore(&mut self, pages: &mut Pages<'_>) { 228 - let fragments = std::mem::take(&mut self.0); 229 - for (url, items) in fragments { 230 - pages.pages.get_mut(&url).unwrap().fragments = items; 231 - } 232 - } 233 - } 234 - 235 - /// Drop bomb 236 - impl Drop for Fragments { 237 - fn drop(&mut self) { 238 - if !self.0.is_empty() { 239 - unreachable!("page fragments were not restored") 240 - } 241 145 } 242 146 }
+4 -5
crates/mdbook-permalinks/src/tests.rs
··· 7 7 use url::Url; 8 8 9 9 use mdbookkit::{ 10 - markdown::mdbook_markdown_options, 10 + markdown::default_markdown_options, 11 11 portable_snapshots, test_document, 12 12 testing::{CARGO_WORKSPACE_DIR, TestDocument, setup_logging}, 13 13 }; ··· 37 37 .join("crates/")? 38 38 .join(concat!(env!("CARGO_PKG_NAME"), "/"))? 39 39 .join("src/")?, 40 - markdown: mdbook_markdown_options(), 40 + markdown: default_markdown_options(), 41 41 config: Config { 42 42 book_url: Some("https://example.org/book".parse::<Url>()?.into()), 43 43 ..Default::default() 44 44 }, 45 45 }; 46 46 47 - let mut pages = Pages::new(mdbook_markdown_options()); 47 + let mut pages = Pages::new(default_markdown_options()); 48 48 49 49 for doc in TEST_DOCUMENTS { 50 50 pages.insert(doc.url(), doc.content)?; ··· 77 77 }; 78 78 } 79 79 80 - test_output!["tests/links.md", "tests/headings.md",]; 80 + test_output!["tests/links.md",]; 81 81 82 82 macro_rules! matcher { 83 83 ( $pattern:pat ) => { ··· 92 92 #[case("_stderr.permalink", matcher!(LinkStatus::Permalink))] 93 93 #[case("_stderr.not-checked-in", matcher!(LinkStatus::PathNotCheckedIn))] 94 94 #[case("_stderr.no-such-path", matcher!(LinkStatus::NoSuchPath))] 95 - #[case("_stderr.no-such-fragment", matcher!(LinkStatus::NoSuchFragment))] 96 95 #[case("_stderr.link-error", matcher!(LinkStatus::Error(..)))] 97 96 fn test_stderr(#[case] name: &str, #[case] matcher: impl Fn(&LinkStatus) -> bool) -> Result<()> { 98 97 let Fixture { env, pages } = &*FIXTURE;
-18
crates/mdbook-permalinks/src/tests/headings.md
··· 1 - # primitive types resolved to nightly 2 - 3 - - [u8] 4 - - [u32] 5 - - [f64] 6 - - [char] 7 - - [str] 8 - 9 - # associated items on primitive types 10 - 11 - - [str::parse] 12 - - [f64::MIN_POSITIVE] 13 - 14 - # macro_export 15 - 16 - - [std::vec!] 17 - - [std::format!] 18 - - [tokio::main!]
+6 -30
crates/mdbook-permalinks/src/tests/links.md
··· 12 12 13 13 # book files 14 14 15 - [Anchors](./headings.md) 15 + [Links](./links.md) 16 16 17 17 [main.rs](../main.rs) 18 18 19 19 ![selfie](Macaca_nigra_self-portrait_large.jpg) 20 20 21 - # fragments 22 - 23 - [Fragments](./links.md#fragments) 24 - 25 - [Heading 1](./links.md#heading-sqrt3x-11x2) 26 - 27 - [Heading 2](./links.md#heading-httpsräksmörgåsjosefssonorg) 28 - 29 - [foobar](#heading-pub-async-fn-foobarrt-mut-foo---barself) 30 - 31 - [macro_export](./headings.md#macro_export) 32 - 33 21 # file not found 34 22 35 23 [Cargo.lock](../../Cargo.lock) ··· 38 26 39 27 ![shinjuku.jpg](shinjuku.jpg) 40 28 41 - # fragment not found 42 - 43 - [associated items](../tests/headings.md#associated_items_on_primitive_types) 44 - 45 - ## heading: $\sqrt{3x-1}+(1+x)^2$ 46 - 47 - ## heading: <https://räksmörgås.josefsson.org/> 48 - 49 - ## heading: pub async fn foobar(rt: &mut [Foo]) -> [Bar]\<Self> <!-- omit from toc --> 50 - 51 29 # canonical urls to book 52 30 53 - found: <https://example.org/book/tests/headings> 31 + found: <https://example.org/book/tests/links> 54 32 55 - found: <https://example.org/book/tests/headings.html> 33 + found: <https://example.org/book/tests/links.html> 56 34 57 35 not found: <https://example.org/book/404> 58 36 59 - ignored: <https://example.com/book/headings> 37 + ignored: <https://example.com/book/links> 60 38 61 39 trailing slash, found: <https://example.org/book/tests/trailing-slash/> 62 40 63 41 trailing slash, found: <https://example.org/book/tests/trailing-slash> 64 42 65 - trailing slash, not found: <https://example.org/book/tests/headings/> 43 + trailing slash, not found: <https://example.org/book/tests/links/> 66 44 67 45 # canonical urls to HEAD 68 46 69 47 [permalink](https://github.com/lorem/ipsum/tree/HEAD/LICENSE-APACHE.md) 70 48 71 - [published](https://github.com/lorem/ipsum/tree/HEAD/crates/mdbook-permalinks/src/tests/headings.md) 49 + [published](https://github.com/lorem/ipsum/tree/HEAD/crates/mdbook-permalinks/src/tests/links.md) 72 50 73 51 [file not found](https://github.com/lorem/ipsum/raw/HEAD/crates/mdbook-permalinks/src/tests/shinjuku.jpg) 74 - 75 - [fragment not found](https://github.com/lorem/ipsum/tree/HEAD/crates/mdbook-permalinks/src/tests/headings.md#associated_items_on_primitive_types) 76 52 77 53 # image-in-link 78 54
+6 -21
crates/mdbook-permalinks/src/tests/snaps/_stderr.ignored.snap
··· 1 1 --- 2 2 source: crates/mdbook-permalinks/src/tests.rs 3 + assertion_line: 106 3 4 expression: report 4 5 --- 5 6 info: link is ignored as it is not supported 6 - ╭─[crates/mdbook-permalinks/src/tests/links.md:47:13] 7 - 8 - │ ## heading: <https://räksmörgås.josefsson.org/> 9 - · ─────────────────────────────────── 10 - 11 - │ ## heading: pub async fn foobar(rt: &mut [Foo]) -> [Bar]\<Self> <!-- omit from toc --> 12 - · ───── ───── 7 + ╭─[crates/mdbook-permalinks/src/tests/links.md:37:10] 13 8 14 - │ # canonical urls to book 15 - 16 - │ found: <https://example.org/book/tests/headings> 17 - 18 - │ found: <https://example.org/book/tests/headings.html> 19 - 20 - │ not found: <https://example.org/book/404> 21 - 22 - │ ignored: <https://example.com/book/headings> 23 - · ─────────────────────────────────── 9 + │ ignored: <https://example.com/book/links> 10 + · ──────────────────────────────── 24 11 25 12 │ trailing slash, found: <https://example.org/book/tests/trailing-slash/> 26 13 27 14 │ trailing slash, found: <https://example.org/book/tests/trailing-slash> 28 15 29 - │ trailing slash, not found: <https://example.org/book/tests/headings/> 16 + │ trailing slash, not found: <https://example.org/book/tests/links/> 30 17 31 18 │ # canonical urls to HEAD 32 19 33 20 │ [permalink](https://github.com/lorem/ipsum/tree/HEAD/LICENSE-APACHE.md) 34 21 35 - │ [published](https://github.com/lorem/ipsum/tree/HEAD/crates/mdbook-permalinks/src/tests/headings.md) 22 + │ [published](https://github.com/lorem/ipsum/tree/HEAD/crates/mdbook-permalinks/src/tests/links.md) 36 23 37 24 │ [file not found](https://github.com/lorem/ipsum/raw/HEAD/crates/mdbook-permalinks/src/tests/shinjuku.jpg) 38 - 39 - │ [fragment not found](https://github.com/lorem/ipsum/tree/HEAD/crates/mdbook-permalinks/src/tests/headings.md#associated_items_on_primitive_types) 40 25 41 26 │ # image-in-link 42 27
-46
crates/mdbook-permalinks/src/tests/snaps/_stderr.no-such-fragment.snap
··· 1 - --- 2 - source: crates/mdbook-permalinks/src/tests.rs 3 - expression: report 4 - --- 5 - warning: fragment does not exist in page 6 - ╭─[crates/mdbook-permalinks/src/tests/links.md:43:1] 7 - 8 - │ [associated items](../tests/headings.md#associated_items_on_primitive_types) 9 - · ──────────────────────────────────────┬───────────────────────────────────── 10 - · ╰── #associated_items_on_primitive_types not found in crates/mdbook-permalinks/src/tests/headings.md 11 - 12 - │ ## heading: $\sqrt{3x-1}+(1+x)^2$ 13 - 14 - │ ## heading: <https://räksmörgås.josefsson.org/> 15 - 16 - │ ## heading: pub async fn foobar(rt: &mut [Foo]) -> [Bar]\<Self> <!-- omit from toc --> 17 - 18 - │ # canonical urls to book 19 - 20 - │ found: <https://example.org/book/tests/headings> 21 - 22 - │ found: <https://example.org/book/tests/headings.html> 23 - 24 - │ not found: <https://example.org/book/404> 25 - 26 - │ ignored: <https://example.com/book/headings> 27 - 28 - │ trailing slash, found: <https://example.org/book/tests/trailing-slash/> 29 - 30 - │ trailing slash, found: <https://example.org/book/tests/trailing-slash> 31 - 32 - │ trailing slash, not found: <https://example.org/book/tests/headings/> 33 - 34 - │ # canonical urls to HEAD 35 - 36 - │ [permalink](https://github.com/lorem/ipsum/tree/HEAD/LICENSE-APACHE.md) 37 - 38 - │ [published](https://github.com/lorem/ipsum/tree/HEAD/crates/mdbook-permalinks/src/tests/headings.md) 39 - 40 - │ [file not found](https://github.com/lorem/ipsum/raw/HEAD/crates/mdbook-permalinks/src/tests/shinjuku.jpg) 41 - 42 - │ [fragment not found](https://github.com/lorem/ipsum/tree/HEAD/crates/mdbook-permalinks/src/tests/headings.md#associated_items_on_primitive_types) 43 - · ────────────────────────────────────────────────────────────────────────┬──────────────────────────────────────────────────────────────────────── 44 - · ╰── #associated_items_on_primitive_types not found in crates/mdbook-permalinks/src/tests/headings.md 45 - 46 - ╰────
+9 -18
crates/mdbook-permalinks/src/tests/snaps/_stderr.no-such-path.snap
··· 1 1 --- 2 2 source: crates/mdbook-permalinks/src/tests.rs 3 + assertion_line: 106 3 4 expression: report 4 5 --- 5 6 warning: file does not exist at path 6 - ╭─[crates/mdbook-permalinks/src/tests/links.md:35:1] 7 + ╭─[crates/mdbook-permalinks/src/tests/links.md:23:1] 7 8 8 9 │ [Cargo.lock](../../Cargo.lock) 9 10 · ───────────────┬────────────── ··· 15 16 · ──────────────┬────────────── 16 17 · ╰── file does not exist at path: crates/mdbook-permalinks/src/tests/shinjuku.jpg 17 18 18 - │ # fragment not found 19 - 20 - │ [associated items](../tests/headings.md#associated_items_on_primitive_types) 21 - 22 - │ ## heading: $\sqrt{3x-1}+(1+x)^2$ 23 - 24 - │ ## heading: <https://räksmörgås.josefsson.org/> 25 - 26 - │ ## heading: pub async fn foobar(rt: &mut [Foo]) -> [Bar]\<Self> <!-- omit from toc --> 27 - 28 19 │ # canonical urls to book 29 20 30 - │ found: <https://example.org/book/tests/headings> 21 + │ found: <https://example.org/book/tests/links> 31 22 32 - │ found: <https://example.org/book/tests/headings.html> 23 + │ found: <https://example.org/book/tests/links.html> 33 24 34 25 │ not found: <https://example.org/book/404> 35 26 · ───────────────┬────────────── 36 27 · ╰── file does not exist at path: crates/mdbook-permalinks/src/404.md 37 28 38 - │ ignored: <https://example.com/book/headings> 29 + │ ignored: <https://example.com/book/links> 39 30 40 31 │ trailing slash, found: <https://example.org/book/tests/trailing-slash/> 41 32 42 33 │ trailing slash, found: <https://example.org/book/tests/trailing-slash> 43 34 44 - │ trailing slash, not found: <https://example.org/book/tests/headings/> 45 - · ─────────────────────┬──────────────────── 46 - · ╰── file does not exist at path: crates/mdbook-permalinks/src/tests/headings/index.md 35 + │ trailing slash, not found: <https://example.org/book/tests/links/> 36 + · ───────────────────┬─────────────────── 37 + · ╰── file does not exist at path: crates/mdbook-permalinks/src/tests/links/index.md 47 38 48 39 │ # canonical urls to HEAD 49 40 50 41 │ [permalink](https://github.com/lorem/ipsum/tree/HEAD/LICENSE-APACHE.md) 51 42 52 - │ [published](https://github.com/lorem/ipsum/tree/HEAD/crates/mdbook-permalinks/src/tests/headings.md) 43 + │ [published](https://github.com/lorem/ipsum/tree/HEAD/crates/mdbook-permalinks/src/tests/links.md) 53 44 54 45 │ [file not found](https://github.com/lorem/ipsum/raw/HEAD/crates/mdbook-permalinks/src/tests/shinjuku.jpg) 55 46 · ────────────────────────────────────────────────────┬────────────────────────────────────────────────────
+2 -1
crates/mdbook-permalinks/src/tests/snaps/_stderr.not-checked-in.snap
··· 1 1 --- 2 2 source: crates/mdbook-permalinks/src/tests.rs 3 + assertion_line: 106 3 4 expression: report 4 5 --- 5 6 warning: path to a file outside source control 6 - ╭─[crates/mdbook-permalinks/src/tests/links.md:37:1] 7 + ╭─[crates/mdbook-permalinks/src/tests/links.md:25:1] 7 8 8 9 │ [`//LICENSE-MIT.md`](//LICENSE-MIT.md) 9 10 · ───────────────────┬──────────────────
+2 -1
crates/mdbook-permalinks/src/tests/snaps/_stderr.permalink.snap
··· 1 1 --- 2 2 source: crates/mdbook-permalinks/src/tests.rs 3 + assertion_line: 106 3 4 expression: report 4 5 --- 5 6 info: link converted to permalink ··· 20 21 · ╰── link: https://github.com/lorem/ipsum/tree/dolor/LICENSE-APACHE.md 21 22 22 23 ╰──── 23 - ╭─[crates/mdbook-permalinks/src/tests/links.md:69:1] 24 + ╭─[crates/mdbook-permalinks/src/tests/links.md:47:1] 24 25 25 26 │ [permalink](https://github.com/lorem/ipsum/tree/HEAD/LICENSE-APACHE.md) 26 27 · ───────────────────────────────────┬───────────────────────────────────
+4 -25
crates/mdbook-permalinks/src/tests/snaps/_stderr.published.snap
··· 1 1 --- 2 2 source: crates/mdbook-permalinks/src/tests.rs 3 + assertion_line: 106 3 4 expression: report 4 5 --- 5 6 info: link to book page or file 6 7 ╭─[crates/mdbook-permalinks/src/tests/links.md:15:1] 7 8 8 - │ [Anchors](./headings.md) 9 - · ────────────┬─────────── 10 - · ╰── file: crates/mdbook-permalinks/src/tests/headings.md 9 + │ [Links](./links.md) 10 + · ─────────┬───────── 11 + · ╰── file: crates/mdbook-permalinks/src/tests/links.md 11 12 12 13 │ [main.rs](../main.rs) 13 14 · ──────────┬────────── ··· 16 17 │ ![selfie](Macaca_nigra_self-portrait_large.jpg) 17 18 · ───────────────────────┬─────────────────────── 18 19 · ╰── file: crates/mdbook-permalinks/src/tests/Macaca_nigra_self-portrait_large.jpg 19 - 20 - │ # fragments 21 - 22 - │ [Fragments](./links.md#fragments) 23 - · ────────────────┬──────────────── 24 - · ╰── file: crates/mdbook-permalinks/src/tests/links.md 25 - 26 - │ [Heading 1](./links.md#heading-sqrt3x-11x2) 27 - · ─────────────────────┬───────────────────── 28 - · ╰── file: crates/mdbook-permalinks/src/tests/links.md 29 - 30 - │ [Heading 2](./links.md#heading-httpsräksmörgåsjosefssonorg) 31 - · ─────────────────────────────┬───────────────────────────── 32 - · ╰── file: crates/mdbook-permalinks/src/tests/links.md 33 - 34 - │ [foobar](#heading-pub-async-fn-foobarrt-mut-foo---barself) 35 - · ─────────────────────────────┬──────────────────────────── 36 - · ╰── file: crates/mdbook-permalinks/src/tests/links.md 37 - 38 - │ [macro_export](./headings.md#macro_export) 39 - · ─────────────────────┬──────────────────── 40 - · ╰── file: crates/mdbook-permalinks/src/tests/headings.md 41 20 42 21 ╰────
+16 -17
crates/mdbook-permalinks/src/tests/snaps/_stderr.rewritten.snap
··· 1 1 --- 2 2 source: crates/mdbook-permalinks/src/tests.rs 3 + assertion_line: 106 3 4 expression: report 4 5 --- 5 6 info: link to book page or file rewritten as path ··· 11 12 · │ link: #absolute-paths 12 13 13 14 ╰──── 14 - ╭─[crates/mdbook-permalinks/src/tests/links.md:53:8] 15 + ╭─[crates/mdbook-permalinks/src/tests/links.md:31:8] 15 16 16 - │ found: <https://example.org/book/tests/headings> 17 - · ────────────────────┬──────────────────── 18 - · ╰─┤ file: crates/mdbook-permalinks/src/tests/headings.md 19 - · │ link: headings.md 17 + │ found: <https://example.org/book/tests/links> 18 + · ───────────────────┬────────────────── 19 + · ╰─┤ file: crates/mdbook-permalinks/src/tests/links.md 20 + · │ link: 20 21 21 - │ found: <https://example.org/book/tests/headings.html> 22 - · ───────────────────────┬────────────────────── 23 - · ╰─┤ file: crates/mdbook-permalinks/src/tests/headings.md 24 - · │ link: headings.md 22 + │ found: <https://example.org/book/tests/links.html> 23 + · ─────────────────────┬───────────────────── 24 + · ╰─┤ file: crates/mdbook-permalinks/src/tests/links.md 25 + · │ link: 25 26 26 27 │ not found: <https://example.org/book/404> 27 28 28 - │ ignored: <https://example.com/book/headings> 29 + │ ignored: <https://example.com/book/links> 29 30 30 31 │ trailing slash, found: <https://example.org/book/tests/trailing-slash/> 31 32 · ────────────────────────┬─────────────────────── ··· 37 38 · ╰─┤ file: crates/mdbook-permalinks/src/tests/trailing-slash/index.md 38 39 · │ link: trailing-slash/index.md 39 40 40 - │ trailing slash, not found: <https://example.org/book/tests/headings/> 41 + │ trailing slash, not found: <https://example.org/book/tests/links/> 41 42 42 43 │ # canonical urls to HEAD 43 44 44 45 │ [permalink](https://github.com/lorem/ipsum/tree/HEAD/LICENSE-APACHE.md) 45 46 46 - │ [published](https://github.com/lorem/ipsum/tree/HEAD/crates/mdbook-permalinks/src/tests/headings.md) 47 - · ──────────────────────────────────────────────────┬───────────────────────────────────────────────── 48 - · ╰─┤ file: crates/mdbook-permalinks/src/tests/headings.md 49 - · │ link: headings.md 47 + │ [published](https://github.com/lorem/ipsum/tree/HEAD/crates/mdbook-permalinks/src/tests/links.md) 48 + · ────────────────────────────────────────────────┬──────────────────────────────────────────────── 49 + · ╰─┤ file: crates/mdbook-permalinks/src/tests/links.md 50 + · │ link: 50 51 51 52 │ [file not found](https://github.com/lorem/ipsum/raw/HEAD/crates/mdbook-permalinks/src/tests/shinjuku.jpg) 52 - 53 - │ [fragment not found](https://github.com/lorem/ipsum/tree/HEAD/crates/mdbook-permalinks/src/tests/headings.md#associated_items_on_primitive_types) 54 53 55 54 │ # image-in-link 56 55
-22
crates/mdbook-permalinks/src/tests/snaps/headings.snap
··· 1 - --- 2 - source: crates/mdbookkit/tests/link_forever.rs 3 - expression: output 4 - --- 5 - # primitive types resolved to nightly 6 - 7 - - [u8] 8 - - [u32] 9 - - [f64] 10 - - [char] 11 - - [str] 12 - 13 - # associated items on primitive types 14 - 15 - - [str::parse] 16 - - [f64::MIN_POSITIVE] 17 - 18 - # macro_export 19 - 20 - - [std::vec!] 21 - - [std::format!] 22 - - [tokio::main!]
+7 -30
crates/mdbook-permalinks/src/tests/snaps/links.snap
··· 1 1 --- 2 2 source: crates/mdbook-permalinks/src/tests.rs 3 + assertion_line: 62 3 4 expression: output 4 5 --- 5 6 # relative paths ··· 16 17 17 18 # book files 18 19 19 - [Anchors](./headings.md) 20 + [Links](./links.md) 20 21 21 22 [main.rs](../main.rs) 22 23 23 24 ![selfie](Macaca_nigra_self-portrait_large.jpg) 24 25 25 - # fragments 26 - 27 - [Fragments](./links.md#fragments) 28 - 29 - [Heading 1](./links.md#heading-sqrt3x-11x2) 30 - 31 - [Heading 2](./links.md#heading-httpsräksmörgåsjosefssonorg) 32 - 33 - [foobar](#heading-pub-async-fn-foobarrt-mut-foo---barself) 34 - 35 - [macro_export](./headings.md#macro_export) 36 - 37 26 # file not found 38 27 39 28 [Cargo.lock](../../Cargo.lock) ··· 42 31 43 32 ![shinjuku.jpg](shinjuku.jpg) 44 33 45 - # fragment not found 46 - 47 - [associated items](../tests/headings.md#associated_items_on_primitive_types) 48 - 49 - ## heading: $\sqrt{3x-1}+(1+x)^2$ 50 - 51 - ## heading: <https://räksmörgås.josefsson.org/> 52 - 53 - ## heading: pub async fn foobar(rt: &mut [Foo]) -> [Bar]\<Self> <!-- omit from toc --> 54 - 55 34 # canonical urls to book 56 35 57 - found: [https://example.org/book/tests/headings](headings.md) 36 + found: [https://example.org/book/tests/links]() 58 37 59 - found: [https://example.org/book/tests/headings.html](headings.md) 38 + found: [https://example.org/book/tests/links.html]() 60 39 61 40 not found: <https://example.org/book/404> 62 41 63 - ignored: <https://example.com/book/headings> 42 + ignored: <https://example.com/book/links> 64 43 65 44 trailing slash, found: [https://example.org/book/tests/trailing-slash/](trailing-slash/index.md) 66 45 67 46 trailing slash, found: [https://example.org/book/tests/trailing-slash](trailing-slash/index.md) 68 47 69 - trailing slash, not found: <https://example.org/book/tests/headings/> 48 + trailing slash, not found: <https://example.org/book/tests/links/> 70 49 71 50 # canonical urls to HEAD 72 51 73 52 [permalink](https://github.com/lorem/ipsum/tree/dolor/LICENSE-APACHE.md) 74 53 75 - [published](headings.md) 54 + [published]() 76 55 77 56 [file not found](https://github.com/lorem/ipsum/raw/HEAD/crates/mdbook-permalinks/src/tests/shinjuku.jpg) 78 - 79 - [fragment not found](https://github.com/lorem/ipsum/tree/HEAD/crates/mdbook-permalinks/src/tests/headings.md#associated_items_on_primitive_types) 80 57 81 58 # image-in-link 82 59
+9 -7
crates/mdbook-permalinks/src/vcs.rs
··· 2 2 3 3 use anyhow::{Context, Result, anyhow, bail}; 4 4 use git2::{DescribeOptions, Repository}; 5 + use mdbook_preprocessor::config::Config as MDBookConfig; 5 6 use tap::{Pipe, Tap, TapFallible}; 6 7 use url::{Url, form_urlencoded::Serializer as SearchParams}; 7 8 ··· 10 11 use crate::{Config, VersionControl}; 11 12 12 13 impl VersionControl { 13 - pub fn try_from_git(config: &Config, book: &mdbook::Config) -> Result<Result<Self>> { 14 + pub fn try_from_git(config: &Config, book: &MDBookConfig) -> Result<Result<Self>> { 14 15 let repo = match Repository::open_from_env() 15 16 .context("preprocessor requires a git repository to work") 16 17 .context("failed to find a git repository") ··· 315 316 } 316 317 } 317 318 318 - fn find_git_remote(repo: &Repository, config: &mdbook::Config) -> Result<Result<RepoSource>> { 319 + fn find_git_remote(repo: &Repository, config: &MDBookConfig) -> Result<Result<RepoSource>> { 319 320 if let Some(url) = config 320 - .get_deserialized_opt::<String, _>("output.html.git-repository-url") 321 + .get::<String>("output.html.git-repository-url") 321 322 .context("failed to get `output.html.git-repository-url`")? 322 323 { 323 324 gix_url::parse(url.as_str().into())? ··· 373 374 mod tests { 374 375 use anyhow::Result; 375 376 use git2::Repository; 377 + use mdbook_preprocessor::config::Config as MDBookConfig; 376 378 377 379 use super::{CustomPermalink, PermalinkFormat, find_git_remote, remote_as_github}; 378 380 ··· 382 384 [output.html] 383 385 git-repository-url = "https://github.com/lorem/ipsum/tree/main/crates/dolor" 384 386 "# 385 - .parse::<mdbook::Config>()?; 387 + .parse::<MDBookConfig>()?; 386 388 let repo = Repository::open_from_env()?; 387 389 let repo = find_git_remote(&repo, &config)??; 388 390 let (owner, repo) = remote_as_github(repo.as_ref())?; ··· 393 395 394 396 #[test] 395 397 fn test_github_url_from_repo() -> Result<()> { 396 - let config = "".parse::<mdbook::Config>()?; 398 + let config = "".parse::<MDBookConfig>()?; 397 399 let repo = Repository::open_from_env()?; 398 400 let repo = find_git_remote(&repo, &config)??; 399 401 let (_, repo) = remote_as_github(repo.as_ref())?; ··· 407 409 [output.html] 408 410 git-repository-url = "git@my-alt.github.com:lorem/ipsum.git" 409 411 "# 410 - .parse::<mdbook::Config>()?; 412 + .parse::<MDBookConfig>()?; 411 413 let repo = Repository::open_from_env()?; 412 414 let repo = find_git_remote(&repo, &config)??; 413 415 let (owner, repo) = remote_as_github(repo.as_ref())?; ··· 423 425 [output.html] 424 426 git-repository-url = "https://gitlab.haskell.org/ghc/ghc" 425 427 "# 426 - .parse::<mdbook::Config>() 428 + .parse::<MDBookConfig>() 427 429 .unwrap(); 428 430 let repo = Repository::open_from_env().unwrap(); 429 431 let repo = find_git_remote(&repo, &config).unwrap().unwrap();
+2 -2
crates/mdbook-rustdoc-links/Cargo.toml
··· 25 25 dirs = { version = "6.0.0" } 26 26 log = { workspace = true } 27 27 lsp-types = { version = "0.95.0" } 28 - mdbook = { workspace = true } 28 + mdbook-markdown = { workspace = true } 29 + mdbook-preprocessor = { workspace = true } 29 30 mdbookkit = { workspace = true } 30 31 miette = { workspace = true } 31 32 proc-macro2 = { version = "1.0.94", features = ["span-locations"] } 32 - pulldown-cmark = { workspace = true } 33 33 serde = { workspace = true, features = ["rc"] } 34 34 serde_json = { workspace = true } 35 35 sha2 = { version = "0.10.8" }
+3 -20
crates/mdbook-rustdoc-links/src/env.rs
··· 8 8 use anyhow::{Context, Result, anyhow, bail}; 9 9 use cargo_toml::{Manifest, Product}; 10 10 use lsp_types::Url; 11 - use pulldown_cmark::Options; 12 11 use serde::{Deserialize, Serialize, de::DeserializeOwned}; 13 12 use shlex::Shlex; 14 13 use tap::Pipe; 15 14 use tokio::process::Command; 16 15 17 - use mdbookkit::{error::OnWarning, markdown::mdbook_markdown_options}; 16 + use mdbookkit::{error::OnWarning, markdown::default_markdown_options}; 18 17 19 18 use super::markdown; 20 19 ··· 65 64 #[arg(long, value_name("PATH"), value_hint(clap::ValueHint::DirPath))] 66 65 pub manifest_dir: Option<PathBuf>, 67 66 67 + // TODO: warn on cache miss due to failed resolution 68 68 /// Directory in which to persist build cache. 69 69 /// 70 70 /// Setting this will enable caching. Will skip rust-analyzer if cache hits. ··· 78 78 #[serde(default)] 79 79 #[arg(long, value_enum, value_name("MODE"), default_value_t = Default::default())] 80 80 pub fail_on_warnings: OnWarning, 81 - 82 - /// Whether to enable punctuations like smart quotes `“”`. 83 - /// 84 - /// This is only meaningful if your links happen to have visible text that has 85 - /// specific punctuation. The processor otherwise passes through the rest of your 86 - /// Markdown source untouched. 87 - /// 88 - /// **In `book.toml`** — this option is not needed because 89 - /// `output.html.smart-punctuation` is honored. 90 - #[serde(default)] 91 - #[arg(long)] 92 - pub smart_punctuation: bool, 93 81 94 82 #[serde(default)] 95 83 #[arg(long, hide = true)] ··· 228 216 } 229 217 230 218 pub fn markdown<'a>(&self, source: &'a str) -> markdown::MarkdownStream<'a> { 231 - let options = if self.config.smart_punctuation { 232 - Options::ENABLE_SMART_PUNCTUATION 233 - } else { 234 - Options::empty() 235 - }; 236 - markdown::stream(source, options.union(mdbook_markdown_options())) 219 + markdown::stream(source, default_markdown_options()) 237 220 } 238 221 239 222 pub fn emit_config(&self) -> EmitConfig {
+2 -2
crates/mdbook-rustdoc-links/src/link.rs
··· 2 2 3 3 use anyhow::{Result, bail}; 4 4 use lsp_types::Url; 5 - use pulldown_cmark::{CowStr, Event, LinkType, Tag, TagEnd}; 5 + use mdbook_markdown::pulldown_cmark::{CowStr, Event, LinkType, Tag, TagEnd}; 6 6 use serde::{Deserialize, Serialize}; 7 7 use tap::{Pipe, Tap, TapFallible}; 8 8 ··· 190 190 slice::Iter, 191 191 }; 192 192 193 - use pulldown_cmark::Event; 193 + use mdbook_markdown::pulldown_cmark::Event; 194 194 195 195 pub type EmitLink<'a> = 196 196 Chain<Chain<Once<Event<'a>>, Cloned<Iter<'a, Event<'a>>>>, Once<Event<'a>>>;
+19 -31
crates/mdbook-rustdoc-links/src/main.rs
··· 1 - use std::{ 2 - borrow::Borrow, 3 - collections::HashMap, 4 - hash::Hash, 5 - io::{Read, Write}, 6 - sync::Arc, 7 - }; 1 + use std::{borrow::Borrow, collections::HashMap, hash::Hash, io::Write, sync::Arc}; 8 2 9 3 use anyhow::{Context, Result}; 10 4 use clap::{Parser, Subcommand}; 11 5 use console::colors_enabled_stderr; 12 6 use log::LevelFilter; 13 7 use lsp_types::Position; 14 - use mdbook::preprocess::PreprocessorContext; 8 + use mdbook_preprocessor::PreprocessorContext; 15 9 use tap::{Pipe, TapFallible}; 16 10 use tokio::task::JoinSet; 17 11 18 12 use mdbookkit::{ 19 - book::{ 20 - book_from_stdin, book_into_stdout, config_from_book, for_each_chapter_mut, iter_chapters, 21 - smart_punctuation, 22 - }, 13 + book::{BookConfigHelper, BookHelper, book_from_stdin, string_from_stdin}, 23 14 diagnostics::Issue, 24 15 log_debug, log_warning, 25 16 logging::{ConsoleLogger, is_logging, spinner}, ··· 176 167 177 168 #[tokio::main] 178 169 async fn main() -> Result<()> { 179 - ConsoleLogger::install(env!("CARGO_PKG_NAME")); 170 + ConsoleLogger::install(PREPROCESSOR_NAME); 180 171 match Program::parse().command { 181 172 Some(Command::Supports { .. }) => Ok(()), 182 173 Some(Command::Markdown(options)) => markdown(options).await, ··· 225 216 226 217 let mut content = Pages::default(); 227 218 228 - for (path, ch) in iter_chapters(&book) { 219 + for (path, ch) in book.iter_chapters() { 229 220 let stream = client.env().markdown(&ch.content).into_offset_iter(); 230 221 content 231 222 .read(path.clone(), &ch.content, stream) ··· 242 233 .await 243 234 .context("failed to resolve some links")?; 244 235 245 - let mut result = iter_chapters(&book) 236 + let mut result = book 237 + .iter_chapters() 246 238 .filter_map(|(path, _)| { 247 239 let output = content 248 240 .emit(path, &client.env().emit_config()) ··· 268 260 FileCache::save(&env, &content).await.ok(); 269 261 } 270 262 271 - for_each_chapter_mut(&mut book, |path, ch| { 272 - if let Some(output) = result.remove(&path) { 263 + book.for_each_chapter_mut(|ch| { 264 + if let Some(path) = &ch.source_path 265 + && let Some(output) = result.remove(path) 266 + { 273 267 ch.content = output 274 268 } 275 269 }); 276 270 277 - book_into_stdout(&book)?; 271 + book.to_stdout()?; 278 272 279 273 env.config.fail_on_warnings.check(status.level())?; 280 274 ··· 348 342 Ok(()) 349 343 } 350 344 351 - fn config(context: &PreprocessorContext) -> Result<Config> { 352 - let mut config = config_from_book::<Config>(&context.config, "rustdoc-link")?; 345 + fn config(ctx: &PreprocessorContext) -> Result<Config> { 346 + let mut config = ctx.config.preprocessor::<Config>(PREPROCESSOR_NAME)?; 353 347 354 348 if let Some(path) = config.manifest_dir { 355 - config.manifest_dir = Some(context.root.join(path)) 349 + config.manifest_dir = Some(ctx.root.join(path)) 356 350 } else { 357 - config.manifest_dir = Some(context.root.clone()) 351 + config.manifest_dir = Some(ctx.root.clone()) 358 352 } 359 353 360 354 if let Some(path) = config.cache_dir { 361 - config.cache_dir = Some(context.root.join(path)) 355 + config.cache_dir = Some(ctx.root.join(path)) 362 356 } 363 357 364 - config.smart_punctuation = smart_punctuation(&context.config); 365 - 366 358 Ok(config) 367 359 } 368 360 369 - fn string_from_stdin() -> Result<String> { 370 - Vec::new() 371 - .pipe(|mut buf| std::io::stdin().read_to_end(&mut buf).and(Ok(buf)))? 372 - .pipe(|buf| Ok(String::from_utf8(buf)?)) 373 - } 361 + static UNIQUE_ID: &str = "__ded48f4d_0c4f_4950_b17d_55fd3b2a0c86__"; 374 362 375 - const UNIQUE_ID: &str = "__ded48f4d_0c4f_4950_b17d_55fd3b2a0c86__"; 363 + static PREPROCESSOR_NAME: &str = env!("CARGO_PKG_NAME");
+8 -6
crates/mdbook-rustdoc-links/src/markdown.rs
··· 1 - use pulldown_cmark::{BrokenLink, BrokenLinkCallback, CowStr, Event, Options, Parser}; 1 + use mdbook_markdown::pulldown_cmark::{ 2 + BrokenLink, BrokenLinkCallback, CowStr, Event, Options, Parser, 3 + }; 2 4 use tap::Pipe; 3 5 4 - use mdbookkit::markdown::mdbook_markdown_options; 6 + use mdbookkit::markdown::default_markdown_options; 5 7 6 8 pub fn stream(text: &str, options: Options) -> MarkdownStream<'_> { 7 9 Parser::new_with_broken_link_callback(text, options, Some(ItemLinks)) ··· 12 14 /// [`BrokenLinkCallback`] implementation that unconditionally converts all "broken" 13 15 /// links to links to be further processed. 14 16 /// 15 - /// "Broken" links are links like `[text][link::item]` that don't have associated URLs, 16 - /// which are actually exactly what [rustdoc_link][super] wants. 17 + /// "Broken" links are links like `[text][link::item]` that don't have associated URLs 18 + /// that are expected for this preprocessor. 17 19 /// 18 20 /// Links that are "broken" that aren't actually doc links won't show up in the output, 19 21 /// because the preprocessor ignores links that cannot be parsed and is capable of ··· 23 25 impl ItemLinks { 24 26 // Explicitly disable smart punctuation to prevent quotes from being changed 25 27 // or else things like lifetimes may become invalid 26 - const OPTIONS: pulldown_cmark::Options = 27 - mdbook_markdown_options().intersection(Options::ENABLE_SMART_PUNCTUATION.complement()); 28 + const OPTIONS: Options = 29 + default_markdown_options().intersection(Options::ENABLE_SMART_PUNCTUATION.complement()); 28 30 } 29 31 30 32 impl<'input> BrokenLinkCallback<'input> for ItemLinks {
+1 -1
crates/mdbook-rustdoc-links/src/page.rs
··· 6 6 }; 7 7 8 8 use anyhow::{Context, Result, bail}; 9 - use pulldown_cmark::{CowStr, Event, Tag, TagEnd}; 9 + use mdbook_markdown::pulldown_cmark::{CowStr, Event, Tag, TagEnd}; 10 10 use tap::Pipe; 11 11 12 12 use mdbookkit::markdown::{PatchStream, Spanned};
+2 -2
crates/mdbookkit/Cargo.toml
··· 24 24 env_logger = { workspace = true } 25 25 indicatif = { version = "0.17.11" } 26 26 log = { workspace = true } 27 - mdbook = { workspace = true } 27 + mdbook-markdown = { workspace = true } 28 + mdbook-preprocessor = { workspace = true } 28 29 miette = { workspace = true } 29 30 owo-colors = { version = "4.2.0" } 30 - pulldown-cmark = { workspace = true } 31 31 pulldown-cmark-to-cmark = { workspace = true } 32 32 serde = { workspace = true } 33 33 serde_json = { workspace = true }
+75 -48
crates/mdbookkit/src/book.rs
··· 4 4 }; 5 5 6 6 use anyhow::{Context, Result}; 7 - use mdbook::{ 8 - BookItem, 9 - book::{Book, Chapter}, 10 - preprocess::PreprocessorContext, 7 + use mdbook_markdown::pulldown_cmark::Options as MarkdownOptions; 8 + use mdbook_preprocessor::{ 9 + PreprocessorContext, 10 + book::{Book, BookItem, Chapter}, 11 + config::{Config as MDBookConfig, HtmlConfig}, 11 12 }; 12 - use serde::de::DeserializeOwned; 13 + use serde::Deserialize; 13 14 use tap::Pipe; 14 15 15 - pub fn book_from_stdin() -> Result<(PreprocessorContext, Book)> { 16 + use crate::markdown::default_markdown_options; 17 + 18 + pub fn string_from_stdin() -> Result<String> { 16 19 Ok(Vec::new() 17 20 .pipe(|mut buf| std::io::stdin().read_to_end(&mut buf).and(Ok(buf)))? 18 - .pipe(String::from_utf8)? 19 - .pipe_as_ref(serde_json::from_str)?) 21 + .pipe(String::from_utf8)?) 20 22 } 21 23 22 - pub fn config_from_book<T>(config: &mdbook::Config, name: &str) -> Result<T> 23 - where 24 - T: DeserializeOwned + Default, 25 - { 26 - let name = name.strip_prefix("mdbook-").unwrap_or(name); 27 - if let Some(config) = config.get_preprocessor(name) { 28 - T::deserialize(toml::Value::Table(config.clone()))? 29 - } else { 30 - Default::default() 31 - } 32 - .pipe(Ok) 24 + /// This uses [`serde_json::from_str`] whereas [`mdbook_preprocessor::parse_input`] uses 25 + /// [`serde_json::from_reader`], which could be slow. 26 + pub fn book_from_stdin() -> Result<(PreprocessorContext, Book)> { 27 + Ok(serde_json::from_str(&string_from_stdin()?)?) 33 28 } 34 29 35 - pub fn smart_punctuation(config: &mdbook::Config) -> bool { 36 - config 37 - .get_deserialized_opt::<bool, _>("output.html.smart-punctuation") 38 - .unwrap_or_default() 39 - .unwrap_or(true) 30 + pub trait BookConfigHelper { 31 + fn preprocessor<'de, T>(&self, name: &str) -> Result<T> 32 + where 33 + T: Deserialize<'de> + Default; 34 + 35 + fn markdown_options(&self) -> MarkdownOptions; 40 36 } 41 37 42 - pub fn iter_chapters(book: &Book) -> impl Iterator<Item = (&PathBuf, &Chapter)> { 43 - book.iter().filter_map(|item| { 44 - let BookItem::Chapter(ch) = item else { 45 - return None; 46 - }; 47 - let Some(path) = &ch.source_path else { 48 - return None; 49 - }; 50 - Some((path, ch)) 51 - }) 38 + impl BookConfigHelper for MDBookConfig { 39 + fn preprocessor<'de, T>(&self, name: &str) -> Result<T> 40 + where 41 + T: Deserialize<'de> + Default, 42 + { 43 + let name = name.strip_prefix("mdbook-").unwrap_or(name); 44 + let name = format!("preprocessor.{name}"); 45 + Ok(self.get::<T>(&name)?.unwrap_or_default()) 46 + } 47 + 48 + fn markdown_options(&self) -> MarkdownOptions { 49 + let HtmlConfig { 50 + smart_punctuation, 51 + definition_lists, 52 + admonitions, 53 + .. 54 + } = self 55 + .get::<HtmlConfig>("output.html") 56 + .unwrap_or_default() 57 + .unwrap_or_default(); 58 + let mut options = default_markdown_options(); 59 + if admonitions { 60 + options.insert(MarkdownOptions::ENABLE_GFM); 61 + } 62 + if smart_punctuation { 63 + options.insert(MarkdownOptions::ENABLE_SMART_PUNCTUATION); 64 + } 65 + if definition_lists { 66 + options.insert(MarkdownOptions::ENABLE_DEFINITION_LIST); 67 + } 68 + options 69 + } 52 70 } 53 71 54 - pub fn for_each_chapter_mut<F>(book: &mut Book, mut func: F) 55 - where 56 - F: FnMut(PathBuf, &mut Chapter), 57 - { 58 - book.for_each_mut(|item| { 59 - let BookItem::Chapter(ch) = item else { return }; 60 - let Some(path) = &ch.source_path else { return }; 61 - func(path.clone(), ch) 62 - }); 72 + pub trait BookHelper { 73 + fn iter_chapters(&self) -> impl Iterator<Item = (&PathBuf, &Chapter)>; 74 + 75 + fn to_stdout(&self) -> Result<()>; 63 76 } 64 77 65 - pub fn book_into_stdout(book: &Book) -> Result<()> { 66 - serde_json::to_string(&book) 67 - .context("failed to serialize book") 68 - .and_then(|output| Ok(std::io::stdout().write_all(output.as_bytes())?)) 69 - .context("failed to write book to stdout") 78 + impl BookHelper for Book { 79 + fn iter_chapters(&self) -> impl Iterator<Item = (&PathBuf, &Chapter)> { 80 + self.iter().filter_map(|item| { 81 + let BookItem::Chapter(ch) = item else { 82 + return None; 83 + }; 84 + let Some(path) = &ch.source_path else { 85 + return None; 86 + }; 87 + Some((path, ch)) 88 + }) 89 + } 90 + 91 + fn to_stdout(&self) -> Result<()> { 92 + serde_json::to_string(&self) 93 + .context("failed to serialize book") 94 + .and_then(|output| Ok(std::io::stdout().write_all(output.as_bytes())?)) 95 + .context("failed to write book to stdout") 96 + } 70 97 }
+5 -3
crates/mdbookkit/src/markdown.rs
··· 2 2 3 3 use std::{borrow::Cow, fmt::Write, ops::Range}; 4 4 5 - use pulldown_cmark::{Event, Options}; 5 + use mdbook_markdown::pulldown_cmark::{Event, Options}; 6 6 use pulldown_cmark_to_cmark::{Error, cmark}; 7 7 use tap::Pipe; 8 8 ··· 101 101 } 102 102 } 103 103 104 - /// <https://github.com/rust-lang/mdBook/blob/v0.4.47/src/utils/mod.rs#L197-L208> 105 - pub const fn mdbook_markdown_options() -> Options { 104 + /// <https://github.com/rust-lang/mdBook/blob/v0.5.1/crates/mdbook-markdown/src/lib.rs#L46-L50> 105 + /// 106 + /// See also [`markdown_options`][super::book::BookConfigHelper::markdown_options]. 107 + pub const fn default_markdown_options() -> Options { 106 108 Options::empty() 107 109 .union(Options::ENABLE_TABLES) 108 110 .union(Options::ENABLE_FOOTNOTES)
+4 -1
docs/app/main.css
··· 177 177 background-color: unset; 178 178 border-block-start: none; 179 179 border-block-end: none; 180 - border-inline-start: 0.25rem solid var(--quote-border); 180 + 181 + &:not(.blockquote-tag) { 182 + border-inline-start: 0.25rem solid var(--quote-border); 183 + } 181 184 182 185 > :last-child { 183 186 margin-block-end: 0;
+28 -33
docs/book.toml
··· 1 1 [book] 2 2 authors = ["Tony Wu"] 3 3 language = "en" 4 - multilingual = false 5 4 src = "src" 6 5 title = "mdbookkit" 7 6 ··· 9 8 build-dir = "dist" 10 9 create-missing = false 11 10 extra-watch-dirs = ["app", "../crates"] 12 - 13 - [rust] 14 - edition = "2021" 15 11 16 12 [output.html] 17 13 additional-css = ["app/dist.css"] 18 14 additional-js = ["app/dist.js"] 19 15 default-theme = "ayu" 20 - git-repository-icon = "fa-github" 16 + git-repository-icon = "fab-github" 21 17 git-repository-url = "https://github.com/tonywu6/mdbookkit" 22 18 hash-files = true 23 19 preferred-dark-theme = "ayu" ··· 26 22 [output.html.playground] 27 23 runnable = false 28 24 29 - [preprocessor.rustdoc-link-options] 30 - before = ["rustdoc-link"] 31 - command = "cargo run --package util-clap-reflect -- --reflect rustdoc-link-options" 25 + # [preprocessor.rustdoc-link-options] 26 + # before = ["rustdoc-link"] 27 + # command = "cargo run --package util-clap-reflect -- --reflect rustdoc-link-options" 32 28 33 - [preprocessor.link-forever-options] 34 - before = ["rustdoc-link"] 35 - command = "cargo run --package util-clap-reflect -- --reflect link-forever-options" 29 + # [preprocessor.link-forever-options] 30 + # before = ["rustdoc-link"] 31 + # command = "cargo run --package util-clap-reflect -- --reflect link-forever-options" 36 32 37 - [preprocessor.rustdoc-link] 33 + [preprocessor.rustdoc-links] 38 34 after = ["links"] 39 35 cache-dir = "build" 40 - cargo-features = ["clap/unstable-doc"] 41 - command = "cargo run --package mdbookkit --bin mdbook-rustdoc-link --all-features" 36 + command = "cargo run --package mdbook-rustdoc-links" 42 37 manifest-dir = "." 43 - rust-analyzer = "cargo run --package util-rust-analyzer --all-features -- analyzer" 38 + rust-analyzer = "cargo run --package util-rust-analyzer -- analyzer" 44 39 45 - [preprocessor.link-forever] 46 - after = ["rustdoc-link"] 40 + [preprocessor.permalinks] 41 + after = ["rustdoc-links"] 47 42 always-link = [".rs"] 48 43 book-url = "https://tonywu6.github.io/mdbookkit/" 49 - command = "cargo run --package mdbookkit --bin mdbook-link-forever --all-features" 44 + command = "cargo run --package mdbook-permalinks" 50 45 51 - [preprocessor.rust-analyzer-version] 52 - after = ["rustdoc-link"] 53 - command = "cargo run --package util-rust-analyzer --all-features -- version" 46 + # [preprocessor.rust-analyzer-version] 47 + # after = ["rustdoc-link"] 48 + # command = "cargo run --package util-rust-analyzer --all-features -- version" 54 49 55 - [preprocessor.alerts] 56 - after = ["rustdoc-link"] 57 - command = "cargo bin mdbook-alerts" 50 + # [preprocessor.alerts] 51 + # after = ["rustdoc-link"] 52 + # command = "cargo bin mdbook-alerts" 58 53 59 54 [preprocessor.app] 60 55 command = "deno run --allow-all app/build/preprocessor.ts" 61 56 62 - [_metadata.socials."/"] 63 - image = "src/media/social.webp" 64 - title = "mdbookkit" 57 + # [_metadata.socials."/"] 58 + # image = "src/media/social.webp" 59 + # title = "mdbookkit" 65 60 66 - [_metadata.socials."/rustdoc-link"] 67 - image = "src/rustdoc-link/media/social.webp" 68 - title = "mdbook-rustdoc-link" 61 + # [_metadata.socials."/rustdoc-link"] 62 + # image = "src/rustdoc-link/media/social.webp" 63 + # title = "mdbook-rustdoc-link" 69 64 70 - [_metadata.socials."/link-forever"] 71 - image = "src/link-forever/media/social.webp" 72 - title = "mdbook-link-forever" 65 + # [_metadata.socials."/link-forever"] 66 + # image = "src/link-forever/media/social.webp" 67 + # title = "mdbook-link-forever"
-4
utils/rust-analyzer/Cargo.toml
··· 14 14 clap = { workspace = true } 15 15 flate2 = "1.1.0" 16 16 indicatif = "0.17.11" 17 - mdbook = { workspace = true, optional = true } 18 17 reqwest = { version = "0.12.15", features = ["blocking"] } 19 18 serde_json = { workspace = true } 20 19 tap = { workspace = true } ··· 22 21 zip = { version = "2.6.1", features = [ 23 22 "deflate", # https://github.com/rust-lang/rust-analyzer/blob/2025-03-17/xtask/src/dist.rs#L134 24 23 ], default-features = false } 25 - 26 - [features] 27 - ra-version = ["dep:mdbook"]
+6 -54
utils/rust-analyzer/src/main.rs
··· 39 39 let Self { release, path } = self; 40 40 41 41 let platform = env!("TARGET"); 42 - let url = format!("https://github.com/rust-lang/rust-analyzer/releases/download/{release}/rust-analyzer-{platform}.gz"); 42 + let url = format!( 43 + "https://github.com/rust-lang/rust-analyzer/releases/download/{release}/rust-analyzer-{platform}.gz" 44 + ); 43 45 44 46 let mut res = reqwest::blocking::get(url)?; 45 47 ··· 72 74 let temp = tempfile()?; 73 75 74 76 let platform = env!("TARGET"); 75 - let url = format!("https://github.com/rust-lang/rust-analyzer/releases/download/{release}/rust-analyzer-{platform}.zip"); 77 + let url = format!( 78 + "https://github.com/rust-lang/rust-analyzer/releases/download/{release}/rust-analyzer-{platform}.zip" 79 + ); 76 80 77 81 let mut res = reqwest::blocking::get(url)?; 78 82 ··· 133 137 #[arg(trailing_var_arg = true, allow_hyphen_values = true, hide = true)] 134 138 args: Vec<String>, 135 139 }, 136 - Version { 137 - #[command(subcommand)] 138 - version: Option<Version>, 139 - }, 140 140 } 141 141 142 142 #[derive(clap::Subcommand, Debug)] ··· 164 164 match program.command { 165 165 Command::Download => download.download(), 166 166 Command::Analyzer { args } => analyzer(&download, args), 167 - Command::Version { version } => match version { 168 - Some(Version::Supports { .. }) => Ok(()), 169 - None => { 170 - #[cfg(feature = "ra-version")] 171 - { 172 - ra_version::preprocessor(&download) 173 - } 174 - #[cfg(not(feature = "ra-version"))] 175 - { 176 - panic!("feature `ra-version` not enabled") 177 - } 178 - } 179 - }, 180 167 } 181 168 } 182 169 ··· 225 212 |w| Self(w, p) 226 213 } 227 214 } 228 - 229 - /// Preprocessor to replace `<ra-version>(version)</ra-version>` with the currently 230 - /// used RA version. Used in docs. 231 - #[cfg(feature = "ra-version")] 232 - mod ra_version { 233 - use std::io::{Read, Write}; 234 - 235 - use anyhow::Result; 236 - use mdbook::{book::Book, preprocess::PreprocessorContext, BookItem}; 237 - use tap::Pipe; 238 - 239 - use crate::Download; 240 - 241 - pub fn preprocessor(Download { release, .. }: &Download) -> Result<()> { 242 - let (_, mut book): (PreprocessorContext, Book) = Vec::new() 243 - .pipe(|mut buf| std::io::stdin().read_to_end(&mut buf).and(Ok(buf)))? 244 - .pipe(String::from_utf8)? 245 - .pipe_as_ref(serde_json::from_str)?; 246 - 247 - let version = format!("`{release}`"); 248 - 249 - let tag = "<ra-version>(version)</ra-version>"; 250 - 251 - book.for_each_mut(|page| { 252 - let BookItem::Chapter(page) = page else { 253 - return; 254 - }; 255 - page.content = page.content.replace(tag, &version); 256 - }); 257 - 258 - let output = serde_json::to_string(&book)?; 259 - std::io::stdout().write_all(output.as_bytes())?; 260 - Ok(()) 261 - } 262 - }