mirror of
https://github.com/rust-lang/mdBook.git
synced 2025-12-28 13:51:10 -05:00
Compare commits
824 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e440094b37 | ||
|
|
15cae10ca8 | ||
|
|
dc2062ab36 | ||
|
|
d9ce98d710 | ||
|
|
b59aab56f2 | ||
|
|
b899c48019 | ||
|
|
515a253e97 | ||
|
|
7ddc3df945 | ||
|
|
2f7293af5c | ||
|
|
fa3ae53d46 | ||
|
|
6425c29893 | ||
|
|
d0bb830491 | ||
|
|
d325c601bb | ||
|
|
e9e889f523 | ||
|
|
05edc4421b | ||
|
|
22ea5fe335 | ||
|
|
714c5fb81e | ||
|
|
56ceb627b8 | ||
|
|
c1b2bec7d7 | ||
|
|
8201b411ab | ||
|
|
836546cf0d | ||
|
|
9813802b3e | ||
|
|
fcf8f938d2 | ||
|
|
60aaa7ae31 | ||
|
|
1b584d1746 | ||
|
|
aa4cb9465f | ||
|
|
89a2e39b80 | ||
|
|
3c2b8cd10f | ||
|
|
6b0b42ebcc | ||
|
|
7a3513200f | ||
|
|
3db0c0b9a1 | ||
|
|
2c7aac6d7a | ||
|
|
3ee22fb430 | ||
|
|
16c5ec4d74 | ||
|
|
7e7e779ef7 | ||
|
|
b364e8ea2c | ||
|
|
78325aaccb | ||
|
|
1411ea967a | ||
|
|
d147a85006 | ||
|
|
0f0dce8d6c | ||
|
|
379574dc61 | ||
|
|
6a7de13c6f | ||
|
|
331aad1597 | ||
|
|
7e01cf9e18 | ||
|
|
c922b8aae6 | ||
|
|
b21446898a | ||
|
|
f4b4a331d7 | ||
|
|
aa349e0b7c | ||
|
|
b592b10633 | ||
|
|
d62cf8e883 | ||
|
|
c6844dd771 | ||
|
|
009247be01 | ||
|
|
84b3b7218e | ||
|
|
71ba6c9eb8 | ||
|
|
9d4ee689db | ||
|
|
ffe88d7e29 | ||
|
|
9f930706bb | ||
|
|
24fa615149 | ||
|
|
a72d6002b7 | ||
|
|
5b7abf4714 | ||
|
|
d0ef70e574 | ||
|
|
7525b35383 | ||
|
|
b54e73e3b6 | ||
|
|
59c76fa665 | ||
|
|
c1d982d92b | ||
|
|
3db275d68a | ||
|
|
94e797fba0 | ||
|
|
c3beecc96a | ||
|
|
7aff98a859 | ||
|
|
bbf54d7459 | ||
|
|
dcc642e66d | ||
|
|
2b738d4425 | ||
|
|
b3670ece0e | ||
|
|
30ce7e79ac | ||
|
|
94f7578576 | ||
|
|
e6568a70eb | ||
|
|
0eb23efd44 | ||
|
|
e78a8471c7 | ||
|
|
536873ca26 | ||
|
|
d6ea4e3f7a | ||
|
|
fcceee4761 | ||
|
|
3f39ba82f9 | ||
|
|
7da38715c1 | ||
|
|
c83bbd6319 | ||
|
|
fad3c663f4 | ||
|
|
f8b9054265 | ||
|
|
f26116a491 | ||
|
|
7f59fdd9bd | ||
|
|
45d41eac5f | ||
|
|
2b5890e2ed | ||
|
|
0b9570b160 | ||
|
|
90396c5b76 | ||
|
|
24b76dd879 | ||
|
|
9a9eb0124a | ||
|
|
257374d76b | ||
|
|
1a0c296532 | ||
|
|
9b4ab72a80 | ||
|
|
b1c2e466e7 | ||
|
|
cdea0f6b61 | ||
|
|
e9b0be7090 | ||
|
|
d402a12e88 | ||
|
|
218e200117 | ||
|
|
3d55375f61 | ||
|
|
77e7cfd22b | ||
|
|
76cd39e5e2 | ||
|
|
09e7bb76dc | ||
|
|
28387130c0 | ||
|
|
33d3d9c3ec | ||
|
|
e651f4d734 | ||
|
|
87d2cd9845 | ||
|
|
32abeef088 | ||
|
|
5de9b6841e | ||
|
|
95e0743bc0 | ||
|
|
3c97525743 | ||
|
|
9a65c8ab92 | ||
|
|
a64a7b7470 | ||
|
|
fd4137a9ea | ||
|
|
a3d4febe3e | ||
|
|
7af4b1dfe8 | ||
|
|
ba6bffac5a | ||
|
|
6201e577fe | ||
|
|
cf2459f730 | ||
|
|
45a481049e | ||
|
|
6bcabcbb6b | ||
|
|
ef993e8cc2 | ||
|
|
a3a5386da0 | ||
|
|
3ab911afa1 | ||
|
|
4615ce2f8c | ||
|
|
7cb8087469 | ||
|
|
d1721667b6 | ||
|
|
1038f0b7f5 | ||
|
|
942cc12a74 | ||
|
|
59f2a9bf4e | ||
|
|
75d0f1efd4 | ||
|
|
552e3378cf | ||
|
|
7c0ddff96a | ||
|
|
07e72757d3 | ||
|
|
58f66a146d | ||
|
|
643d5ecc5c | ||
|
|
c712ba7aab | ||
|
|
1450070f73 | ||
|
|
e310dfc605 | ||
|
|
cbfd75a821 | ||
|
|
eaa6914205 | ||
|
|
a76557a678 | ||
|
|
01836ba5d4 | ||
|
|
46ce077de6 | ||
|
|
f7c9180d80 | ||
|
|
9e9cf49c50 | ||
|
|
4c951d530d | ||
|
|
780fb979a0 | ||
|
|
b77942d3c8 | ||
|
|
d0deee90b0 | ||
|
|
e6ac8ecdd9 | ||
|
|
d1f5ecc103 | ||
|
|
e0b247e9d6 | ||
|
|
db8a2821ea | ||
|
|
39d7130019 | ||
|
|
2eccb457d2 | ||
|
|
d1682d27fb | ||
|
|
a94a940ff7 | ||
|
|
daf402e1dc | ||
|
|
5ebd2c0527 | ||
|
|
b349e8abc9 | ||
|
|
e225586953 | ||
|
|
cf7663f800 | ||
|
|
3155c63e88 | ||
|
|
4df9ec90af | ||
|
|
73cabeb904 | ||
|
|
4b773024ae | ||
|
|
33ea661350 | ||
|
|
1b18740b56 | ||
|
|
6fed9e52f9 | ||
|
|
fd59dc73e5 | ||
|
|
146bea48c6 | ||
|
|
efb5bc285d | ||
|
|
5ea8e55aea | ||
|
|
1acf23ff73 | ||
|
|
69cc1fa005 | ||
|
|
2fb489137b | ||
|
|
4d9eb9b4b4 | ||
|
|
f6768b816c | ||
|
|
8f7e030ac3 | ||
|
|
9180dd1659 | ||
|
|
9278b838a8 | ||
|
|
2674347768 | ||
|
|
3d44553671 | ||
|
|
9d5c454e47 | ||
|
|
a00e7d1769 | ||
|
|
60be20a783 | ||
|
|
8746206060 | ||
|
|
f5ae7c4f13 | ||
|
|
dcf9462d1e | ||
|
|
78aa2a16f8 | ||
|
|
65d9eb6f7e | ||
|
|
303db0ddec | ||
|
|
a884c2574e | ||
|
|
60029e4e15 | ||
|
|
4e16d96ed5 | ||
|
|
0eefd63a13 | ||
|
|
89c2743cc6 | ||
|
|
a825427722 | ||
|
|
c99047bbda | ||
|
|
20a0b99c3d | ||
|
|
ec495a7823 | ||
|
|
e38fb1ecc6 | ||
|
|
f37ea9a4e7 | ||
|
|
8f74804c70 | ||
|
|
649f3555e5 | ||
|
|
8432df1e80 | ||
|
|
9eba9ed93a | ||
|
|
b0c6f2d7a3 | ||
|
|
6e0688afef | ||
|
|
e9951af73e | ||
|
|
138dc696b7 | ||
|
|
91b2fb86bf | ||
|
|
d4df7e7cdd | ||
|
|
4699269e49 | ||
|
|
c1ed6ee108 | ||
|
|
f59cfe7e2f | ||
|
|
9268884b17 | ||
|
|
4f435c62e6 | ||
|
|
9a97f0a096 | ||
|
|
bc23d08fa5 | ||
|
|
84d848f292 | ||
|
|
d7df832cce | ||
|
|
406b325c54 | ||
|
|
6d6e5407a3 | ||
|
|
06efa7a675 | ||
|
|
bff36e7229 | ||
|
|
cda28bb618 | ||
|
|
fe1ba71d45 | ||
|
|
23f5ffd6d6 | ||
|
|
484e5c0b8f | ||
|
|
a80febd318 | ||
|
|
16010ee28b | ||
|
|
fb1476d1e3 | ||
|
|
b375f4e3d5 | ||
|
|
25ec7ace1a | ||
|
|
ebc01dbb71 | ||
|
|
7b3e945a27 | ||
|
|
964a10ff29 | ||
|
|
5907caa732 | ||
|
|
da55cf273f | ||
|
|
a6ab4d8402 | ||
|
|
4c2318922f | ||
|
|
b2d50392ea | ||
|
|
a5086a1e58 | ||
|
|
6c4c3448e3 | ||
|
|
5d5c55e619 | ||
|
|
e2023fd72d | ||
|
|
e677b72eb8 | ||
|
|
7e090ca42f | ||
|
|
122c988477 | ||
|
|
d0fe9bd41c | ||
|
|
b1ccb30220 | ||
|
|
91e3aa4b55 | ||
|
|
2d63286c63 | ||
|
|
5dd2a5bff4 | ||
|
|
1b3b10d2ae | ||
|
|
2c26c65f4d | ||
|
|
e8d4bc52e1 | ||
|
|
6038af292f | ||
|
|
578e4da5b6 | ||
|
|
43008ef2ef | ||
|
|
d605938886 | ||
|
|
7e11d37e49 | ||
|
|
50bcf67f2b | ||
|
|
c2d58158da | ||
|
|
1731779a8d | ||
|
|
f7e349d37f | ||
|
|
61c8413138 | ||
|
|
8ee950e3de | ||
|
|
c44ef1b2f0 | ||
|
|
07dfc4b89a | ||
|
|
282e55122e | ||
|
|
17210b058f | ||
|
|
b1cf3f117d | ||
|
|
d665732056 | ||
|
|
2f59dbf1ef | ||
|
|
3a63276727 | ||
|
|
4c64f23089 | ||
|
|
683d2b2240 | ||
|
|
11f95f76e6 | ||
|
|
2732c5e8f7 | ||
|
|
6b550cb4bb | ||
|
|
712362f9e7 | ||
|
|
28ce8f5ac0 | ||
|
|
255756cfee | ||
|
|
53d821bf6d | ||
|
|
d39d4517aa | ||
|
|
bd0f434225 | ||
|
|
3806d7b6ea | ||
|
|
d1b484ff35 | ||
|
|
04c04dfc88 | ||
|
|
1d265fd143 | ||
|
|
8e673c96c2 | ||
|
|
99ecd4f87c | ||
|
|
e839ef0866 | ||
|
|
769cc0a7c1 | ||
|
|
c2686a817a | ||
|
|
bd14d0910a | ||
|
|
6eb597a556 | ||
|
|
5c91041dad | ||
|
|
59568208ff | ||
|
|
21a16c9b75 | ||
|
|
4e8e1e1408 | ||
|
|
2baed040c2 | ||
|
|
101063093b | ||
|
|
f7ffffbd1e | ||
|
|
760c9b0767 | ||
|
|
6016e12b90 | ||
|
|
88684d843d | ||
|
|
b82562fe8a | ||
|
|
44c3213f5d | ||
|
|
fd56a53e76 | ||
|
|
ca4b85b815 | ||
|
|
d7a2b29f06 | ||
|
|
4039c72fd3 | ||
|
|
2bd8bdf798 | ||
|
|
0da7ba4abe | ||
|
|
d6cfa21fff | ||
|
|
95fba3f357 | ||
|
|
d5999849d9 | ||
|
|
8b2659e0f4 | ||
|
|
c4a64ab599 | ||
|
|
6b4e3584b4 | ||
|
|
b8fc7a1b2d | ||
|
|
2ee083dfbe | ||
|
|
1947f8ca65 | ||
|
|
2f59943c04 | ||
|
|
980f943179 | ||
|
|
5e998788e9 | ||
|
|
6a94492238 | ||
|
|
e3717ad47b | ||
|
|
49b7f08164 | ||
|
|
7def6d70e8 | ||
|
|
554f29703f | ||
|
|
730d7f8410 | ||
|
|
b6603468d6 | ||
|
|
441a10bdd7 | ||
|
|
efdb83266a | ||
|
|
ac9c12334a | ||
|
|
2a3088422a | ||
|
|
1f505c2b2e | ||
|
|
a7b3aa0444 | ||
|
|
a9160acd64 | ||
|
|
4c1bca1684 | ||
|
|
8fffb2a704 | ||
|
|
ba37cc8462 | ||
|
|
3ea0f9b745 | ||
|
|
29d8747e01 | ||
|
|
f5549f2267 | ||
|
|
e2a8600712 | ||
|
|
f2cb601c11 | ||
|
|
6e0d0facff | ||
|
|
f79d5d4582 | ||
|
|
820714a560 | ||
|
|
d5535d1226 | ||
|
|
e5f77aaaf2 | ||
|
|
86a368b726 | ||
|
|
1dc482b00d | ||
|
|
21d8f394ae | ||
|
|
c9dae170f3 | ||
|
|
fcf2d7a03b | ||
|
|
2498887dfc | ||
|
|
f04d7b802d | ||
|
|
bfcddf2680 | ||
|
|
2b649fe94f | ||
|
|
fc4236eaa7 | ||
|
|
a592da33bb | ||
|
|
6af6219e5b | ||
|
|
e5f74b6c86 | ||
|
|
84a2ab0dba | ||
|
|
d63ef8330d | ||
|
|
01e50303a2 | ||
|
|
2b3304cb8b | ||
|
|
4448f3fc4b | ||
|
|
859659f197 | ||
|
|
4a93eddae2 | ||
|
|
0173451b67 | ||
|
|
ac1749ff2f | ||
|
|
8cdeb121c5 | ||
|
|
74313bb701 | ||
|
|
3c25dba9b4 | ||
|
|
2387942588 | ||
|
|
93c9ae5700 | ||
|
|
9efa9fd1c4 | ||
|
|
8a33407cc5 | ||
|
|
699844a5c3 | ||
|
|
9bdec5e7cc | ||
|
|
930f730361 | ||
|
|
09c738468f | ||
|
|
a3d1afdd1f | ||
|
|
8e8e53ae15 | ||
|
|
5fe801a7d1 | ||
|
|
a6f317e352 | ||
|
|
ed95252f05 | ||
|
|
a058da8b74 | ||
|
|
73be1292ab | ||
|
|
98ecd1178b | ||
|
|
996ac382c1 | ||
|
|
b88839cc25 | ||
|
|
1ef94c2a7e | ||
|
|
f0ac13e3e2 | ||
|
|
b0ae14a2c7 | ||
|
|
81ab2eb7db | ||
|
|
213171591a | ||
|
|
db13d8e561 | ||
|
|
b4bb44292d | ||
|
|
bb7a863d3e | ||
|
|
e62a9dba87 | ||
|
|
4a94b656cd | ||
|
|
a873d46871 | ||
|
|
ce0c5f1d07 | ||
|
|
33d7e86fb6 | ||
|
|
f9f9785839 | ||
|
|
0c37b912ba | ||
|
|
e880fb6339 | ||
|
|
a8d6337ac6 | ||
|
|
f37a89cd4c | ||
|
|
aaeb3e2852 | ||
|
|
8c4b292d58 | ||
|
|
40159362c0 | ||
|
|
aa67245743 | ||
|
|
d968443074 | ||
|
|
3716123e10 | ||
|
|
50a2ec3cf1 | ||
|
|
07459aef60 | ||
|
|
0f56c09d3a | ||
|
|
63ad3d9340 | ||
|
|
1c5dc1e310 | ||
|
|
77af889a2e | ||
|
|
e48fed74bf | ||
|
|
e512850c13 | ||
|
|
bb412edf53 | ||
|
|
5b0a23ebab | ||
|
|
e56c41a1c2 | ||
|
|
d1b5a8f982 | ||
|
|
f396623b63 | ||
|
|
9ec43b6c6d | ||
|
|
7c4d2070f7 | ||
|
|
50d5917530 | ||
|
|
9cd47eb80f | ||
|
|
4932df2570 | ||
|
|
11d31c989c | ||
|
|
e5ace6d6a4 | ||
|
|
e7c3d02c61 | ||
|
|
d8a68ba3f6 | ||
|
|
d29a79349c | ||
|
|
d6088c8a57 | ||
|
|
b91e5c8807 | ||
|
|
6199e4df79 | ||
|
|
2d11eb05fe | ||
|
|
3d45e40693 | ||
|
|
228e99ba11 | ||
|
|
4b569edadd | ||
|
|
3e652b5bfc | ||
|
|
ba41d73dc3 | ||
|
|
1ce1401263 | ||
|
|
00b3d9cf86 | ||
|
|
bb3398bdbb | ||
|
|
19c26217c0 | ||
|
|
a2029f0a78 | ||
|
|
7c33ac800c | ||
|
|
d371001ab8 | ||
|
|
d73504eb23 | ||
|
|
abddd7c6f7 | ||
|
|
31e36f85e7 | ||
|
|
92a7b0cdcd | ||
|
|
592140db5b | ||
|
|
3a0eeb4bbb | ||
|
|
a9dae326fa | ||
|
|
abba959add | ||
|
|
ea15e55829 | ||
|
|
d07bd9fed4 | ||
|
|
b83c55f7ef | ||
|
|
69a08ef390 | ||
|
|
1cd1151790 | ||
|
|
84d4063e4a | ||
|
|
07830f7f11 | ||
|
|
828b7d05c5 | ||
|
|
379004efcb | ||
|
|
2497e77bf1 | ||
|
|
0c2292b9aa | ||
|
|
4386a10e87 | ||
|
|
3cfed10098 | ||
|
|
a655d5d241 | ||
|
|
f8c3a2deea | ||
|
|
b226d2fc55 | ||
|
|
53ba0d6655 | ||
|
|
43ead86ecc | ||
|
|
1d3ec7e0c7 | ||
|
|
f4017376a9 | ||
|
|
672cf456eb | ||
|
|
8dce00d54d | ||
|
|
4f7c299de7 | ||
|
|
04e74bfa1b | ||
|
|
4026a586a1 | ||
|
|
71281bff10 | ||
|
|
8542f7f29d | ||
|
|
fe492d1cb9 | ||
|
|
481c2f2194 | ||
|
|
882014860c | ||
|
|
e3ec751a3f | ||
|
|
fc565df86b | ||
|
|
ec8e63145c | ||
|
|
2752c88c46 | ||
|
|
e7befd19bc | ||
|
|
644b8e132c | ||
|
|
8e82ae534a | ||
|
|
6a8a5b7642 | ||
|
|
c3284a2ae9 | ||
|
|
df12cc55c8 | ||
|
|
cb4a3e0711 | ||
|
|
9194a40acd | ||
|
|
506996808b | ||
|
|
5163c5ab75 | ||
|
|
ecfaed1e02 | ||
|
|
8bb5426441 | ||
|
|
a674c9eff1 | ||
|
|
7ab939f8f2 | ||
|
|
581187098c | ||
|
|
ab7802a9a9 | ||
|
|
345acb8597 | ||
|
|
6380526e93 | ||
|
|
4560bdeb47 | ||
|
|
0aa3a9045a | ||
|
|
b30b58b565 | ||
|
|
c6220fba83 | ||
|
|
652eab6e7e | ||
|
|
5726a8afd6 | ||
|
|
7e26a8430d | ||
|
|
07a64b110a | ||
|
|
dd69e03ff5 | ||
|
|
7f3a0ff6a0 | ||
|
|
aea317e173 | ||
|
|
f9454615b1 | ||
|
|
39211291d9 | ||
|
|
f01fe854fa | ||
|
|
6eeaaaa44d | ||
|
|
357ebcf7ce | ||
|
|
1a4f38eace | ||
|
|
1d3f83eede | ||
|
|
9712347b9c | ||
|
|
f73d42d994 | ||
|
|
a647017e4b | ||
|
|
a66d44190e | ||
|
|
01fd7a76f0 | ||
|
|
99dc62f9c3 | ||
|
|
b891fd5a12 | ||
|
|
02fa7b0a11 | ||
|
|
8b2e1c2daa | ||
|
|
88d2f69138 | ||
|
|
cb94053779 | ||
|
|
0a8707b1e6 | ||
|
|
0dc2728fa9 | ||
|
|
9b02cd7496 | ||
|
|
11f86f4511 | ||
|
|
4abac12c04 | ||
|
|
d7c7d91005 | ||
|
|
9243cf9d95 | ||
|
|
d2470730fc | ||
|
|
6a2e2461fb | ||
|
|
3faa3e42f0 | ||
|
|
9c8fae4704 | ||
|
|
9b6f5a9840 | ||
|
|
62af2367bb | ||
|
|
37808b7e08 | ||
|
|
b37f21a09b | ||
|
|
966632a724 | ||
|
|
c7281459f9 | ||
|
|
ae3f87ad0c | ||
|
|
c068703028 | ||
|
|
6cbc41d413 | ||
|
|
25c1ca1275 | ||
|
|
acbb951240 | ||
|
|
9e96165d8f | ||
|
|
5c5ef2f86b | ||
|
|
23ac06e2eb | ||
|
|
2ddbb37f49 | ||
|
|
a481735fa2 | ||
|
|
954cfa86e5 | ||
|
|
7e52da3c1b | ||
|
|
4e8d051bd1 | ||
|
|
78ee8e43bb | ||
|
|
b675b91980 | ||
|
|
3d8db7f25c | ||
|
|
3d37e24c14 | ||
|
|
eb19d2d654 | ||
|
|
1052ee92e1 | ||
|
|
3598e905aa | ||
|
|
3f002979c4 | ||
|
|
742dbbc917 | ||
|
|
991a725c26 | ||
|
|
317c7731da | ||
|
|
4c17b11ed0 | ||
|
|
005dfc55bf | ||
|
|
8c86031384 | ||
|
|
5151aae07e | ||
|
|
42b87e0fbc | ||
|
|
33add4b532 | ||
|
|
b0513ee771 | ||
|
|
b4538da9c3 | ||
|
|
7ac3e50b37 | ||
|
|
13a9aab2b2 | ||
|
|
eccec9bb52 | ||
|
|
e63f53fe47 | ||
|
|
2c20c99d4a | ||
|
|
c6125b184f | ||
|
|
dfb6e3cb10 | ||
|
|
cffc385b0c | ||
|
|
e73928f933 | ||
|
|
41071a5dd9 | ||
|
|
f6a7432569 | ||
|
|
89ea60e7a5 | ||
|
|
10b69e60c8 | ||
|
|
336e08fe50 | ||
|
|
5bfdf9fcc8 | ||
|
|
29f8b791f1 | ||
|
|
877bf37d18 | ||
|
|
d2565af000 | ||
|
|
599e47f1f1 | ||
|
|
0c31ab2953 | ||
|
|
b1c7c54108 | ||
|
|
f654c42426 | ||
|
|
0c926b3e88 | ||
|
|
e4eddb3f26 | ||
|
|
adec78e7f5 | ||
|
|
5cd5e4764c | ||
|
|
132f4fd358 | ||
|
|
1d72cea972 | ||
|
|
1aa1194d79 | ||
|
|
304234c122 | ||
|
|
729c94a7e4 | ||
|
|
df874cdbdb | ||
|
|
5dce539928 | ||
|
|
206a00915b | ||
|
|
ced74ca4dd | ||
|
|
09667c9956 | ||
|
|
d729a762fe | ||
|
|
43b3d157d9 | ||
|
|
a9f3be6f44 | ||
|
|
34356b87a0 | ||
|
|
48c97dadd0 | ||
|
|
65198a7632 | ||
|
|
a0e7b19784 | ||
|
|
7e2e095c26 | ||
|
|
5baaf55abc | ||
|
|
9157f6e32d | ||
|
|
3688f73052 | ||
|
|
3e89e8b1bd | ||
|
|
e08fc148b1 | ||
|
|
d9c1c77aae | ||
|
|
bb2ca4f938 | ||
|
|
42aded9577 | ||
|
|
7fb2d5437a | ||
|
|
4cc3a1333b | ||
|
|
322e8fcf77 | ||
|
|
a8a460545f | ||
|
|
1d69ccae48 | ||
|
|
762d89ebbf | ||
|
|
91ffca1bbc | ||
|
|
6f963bbe3c | ||
|
|
93af92910a | ||
|
|
f30ce0184d | ||
|
|
ccb2340fbe | ||
|
|
bbe6e324d0 | ||
|
|
a776aa9783 | ||
|
|
b8f8e76899 | ||
|
|
ac4e00c7c6 | ||
|
|
67fde37030 | ||
|
|
b2eb1ace08 | ||
|
|
b5fd170008 | ||
|
|
b3665c287d | ||
|
|
436c084b9e | ||
|
|
47f85e71a8 | ||
|
|
1d448fc8cc | ||
|
|
add23a43c2 | ||
|
|
8ba1830750 | ||
|
|
76c1c9e0a8 | ||
|
|
d054140117 | ||
|
|
512826c465 | ||
|
|
99019b74aa | ||
|
|
d87e77edd0 | ||
|
|
abfc3009fc | ||
|
|
028c8b0f75 | ||
|
|
05f3c693a7 | ||
|
|
8b3038e3ef | ||
|
|
bc432c8f42 | ||
|
|
e88970d172 | ||
|
|
4c87a0b5f0 | ||
|
|
ac38f05bb6 | ||
|
|
3119a7e4bf | ||
|
|
cc745d04f2 | ||
|
|
d1a23109e2 | ||
|
|
b3e0942bc9 | ||
|
|
3e998eb766 | ||
|
|
2bbabdcd62 | ||
|
|
a287a0dcc8 | ||
|
|
e7afb3340c | ||
|
|
b4e15e5357 | ||
|
|
1d1d4d7c30 | ||
|
|
35c2d1ff91 | ||
|
|
fd9d27e082 | ||
|
|
0e1787c617 | ||
|
|
e5563182fc | ||
|
|
a08255316a | ||
|
|
ebea8c2337 | ||
|
|
a7342230d5 | ||
|
|
15b18a682b | ||
|
|
a8590928cf | ||
|
|
c9a9987aec | ||
|
|
775377acce | ||
|
|
5dd0496a4f | ||
|
|
f300a21a47 | ||
|
|
0f89abb1c7 | ||
|
|
b5e32f57dc | ||
|
|
b88abb171c | ||
|
|
7c8dd5085b | ||
|
|
4f793af53b | ||
|
|
29b3ff14c7 | ||
|
|
0ac36f2183 | ||
|
|
5835da2432 | ||
|
|
646e3f8fd2 | ||
|
|
da9be67516 | ||
|
|
d9dbba49ea | ||
|
|
384582aeba | ||
|
|
e94078cc9c | ||
|
|
e1a46d213e | ||
|
|
62c8311301 | ||
|
|
b8011de3e8 | ||
|
|
019e74041d | ||
|
|
8cd7061ff2 | ||
|
|
96b99472fd | ||
|
|
4d357b6779 | ||
|
|
1e6328c112 | ||
|
|
21c24c2815 | ||
|
|
bb8b43d396 | ||
|
|
f07e734efc | ||
|
|
db2c16102e | ||
|
|
cae8a8ffe2 | ||
|
|
bdb37ec117 | ||
|
|
01656b610f | ||
|
|
b9ff0e8a77 | ||
|
|
0bda57175d | ||
|
|
374e1d3f94 | ||
|
|
6287e6a44f | ||
|
|
953d3821b6 | ||
|
|
488ace15ff | ||
|
|
b452d5e0c7 | ||
|
|
289028850f | ||
|
|
2a55ff62f3 | ||
|
|
6bf86806e4 | ||
|
|
90bd7207ec | ||
|
|
27b29fdaf2 | ||
|
|
154e0fb308 | ||
|
|
0de177a344 | ||
|
|
f154b2fb65 | ||
|
|
d7759fbf4d | ||
|
|
f84e670edd | ||
|
|
9a9c625319 | ||
|
|
b9ca108fca | ||
|
|
e99dc51fb3 | ||
|
|
7ee5b6643b | ||
|
|
42781bcd6b | ||
|
|
41d372de26 | ||
|
|
69599646e7 | ||
|
|
69fef40e57 | ||
|
|
a323620e02 | ||
|
|
ea0b835b38 | ||
|
|
58f0f3b0f2 | ||
|
|
e7a61efb39 | ||
|
|
d48bc29373 | ||
|
|
72f154bee4 | ||
|
|
1c71eaa964 | ||
|
|
c195aa990d | ||
|
|
34bdcaf8b3 | ||
|
|
41399fc29c | ||
|
|
7f82a197b9 | ||
|
|
71d44933f0 | ||
|
|
f01bf88e69 | ||
|
|
b5ea84c60d | ||
|
|
148c806e34 | ||
|
|
38279deed7 | ||
|
|
55f7ed1c37 | ||
|
|
eb0f7179ab | ||
|
|
5fb3675151 | ||
|
|
77b4f6a940 | ||
|
|
6308da699a | ||
|
|
62a727c041 | ||
|
|
3bc5d907f4 | ||
|
|
3cd12e7092 | ||
|
|
c8bbfd4bc1 | ||
|
|
d48a27f94f | ||
|
|
8c456666ff | ||
|
|
48b0f547c5 | ||
|
|
867fbfec05 | ||
|
|
951c873df6 | ||
|
|
4af155e963 | ||
|
|
07719a8e0e | ||
|
|
cc92d665ca | ||
|
|
b86533b2a1 | ||
|
|
b2ad669c61 | ||
|
|
bb043ef660 | ||
|
|
82aef1bc3f | ||
|
|
38c883e1ef | ||
|
|
8a00a004d8 | ||
|
|
6af77a7792 | ||
|
|
b5ca820345 | ||
|
|
b765023da3 | ||
|
|
d306aed587 | ||
|
|
89a5dbaf9a | ||
|
|
6961247f56 | ||
|
|
07551760c9 | ||
|
|
990daceed5 | ||
|
|
2989096188 | ||
|
|
03c6c44e5b | ||
|
|
31a370d149 | ||
|
|
0bc1030a02 | ||
|
|
43fcd00cd5 | ||
|
|
3d83b784b3 | ||
|
|
5d42738a79 | ||
|
|
1f4dab3e5c |
4
.gitattributes
vendored
4
.gitattributes
vendored
@@ -2,3 +2,7 @@
|
||||
|
||||
* text=auto eol=lf
|
||||
*.rs rust
|
||||
*.woff binary
|
||||
*.ttf binary
|
||||
*.otf binary
|
||||
*.png binary
|
||||
|
||||
42
.github/workflows/deploy.yml
vendored
Normal file
42
.github/workflows/deploy.yml
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
name: Deploy
|
||||
on:
|
||||
release:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Deploy Release
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: Install hub
|
||||
run: ci/install-hub.sh ${{ matrix.os }}
|
||||
shell: bash
|
||||
- name: Install Rust
|
||||
run: ci/install-rust.sh stable
|
||||
shell: bash
|
||||
- name: Build and deploy artifacts
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: ci/make-release.sh ${{ matrix.os }}
|
||||
shell: bash
|
||||
pages:
|
||||
name: GitHub Pages
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: Install Rust (rustup)
|
||||
run: rustup update stable --no-self-update && rustup default stable
|
||||
- name: Build book
|
||||
run: cargo run -- build guide
|
||||
- name: Deploy to GitHub
|
||||
env:
|
||||
GITHUB_DEPLOY_KEY: ${{ secrets.GITHUB_DEPLOY_KEY }}
|
||||
run: |
|
||||
touch guide/book/.nojekyll
|
||||
curl -LsSf https://raw.githubusercontent.com/rust-lang/simpleinfra/master/setup-deploy-keys/src/deploy.rs | rustc - -o /tmp/deploy
|
||||
cd guide/book
|
||||
/tmp/deploy
|
||||
51
.github/workflows/main.yml
vendored
Normal file
51
.github/workflows/main.yml
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
name: CI
|
||||
on:
|
||||
# Only run when merging to master, or open/synchronize/reopen a PR.
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Test
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
build: [stable, beta, nightly, macos, windows, msrv]
|
||||
include:
|
||||
- build: stable
|
||||
os: ubuntu-latest
|
||||
rust: stable
|
||||
- build: beta
|
||||
os: ubuntu-latest
|
||||
rust: beta
|
||||
- build: nightly
|
||||
os: ubuntu-latest
|
||||
rust: nightly
|
||||
- build: macos
|
||||
os: macos-latest
|
||||
rust: stable
|
||||
- build: windows
|
||||
os: windows-latest
|
||||
rust: stable
|
||||
- build: msrv
|
||||
os: ubuntu-latest
|
||||
rust: 1.45.0
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: Install Rust
|
||||
run: bash ci/install-rust.sh ${{ matrix.rust }}
|
||||
- name: Build and run tests
|
||||
run: cargo test
|
||||
- name: Test no default
|
||||
run: cargo test --no-default-features
|
||||
|
||||
rustfmt:
|
||||
name: Rustfmt
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: Install Rust
|
||||
run: rustup update stable && rustup default stable && rustup component add rustfmt
|
||||
- run: cargo fmt -- --check
|
||||
9
.gitignore
vendored
9
.gitignore
vendored
@@ -1,12 +1,17 @@
|
||||
Cargo.lock
|
||||
target
|
||||
|
||||
# MacOS temp file
|
||||
.DS_Store
|
||||
|
||||
book-test
|
||||
book-example/book
|
||||
guide/book
|
||||
|
||||
.vscode
|
||||
tests/dummy_book/book/
|
||||
|
||||
# Ignore Jetbrains specific files.
|
||||
.idea/
|
||||
|
||||
# Ignore Vim temporary and swap files.
|
||||
*.sw?
|
||||
*~
|
||||
|
||||
88
.travis.yml
88
.travis.yml
@@ -1,88 +0,0 @@
|
||||
# Based on the "trust" template v0.1.1
|
||||
# https://github.com/japaric/trust/tree/v0.1.1
|
||||
|
||||
dist: trusty
|
||||
language: rust
|
||||
services: docker
|
||||
sudo: required
|
||||
|
||||
cache: cargo
|
||||
before_cache:
|
||||
- chmod -R a+r $HOME/.cargo
|
||||
|
||||
env:
|
||||
global:
|
||||
- CRATE_NAME=mdbook
|
||||
- secure: DPzSRXyfRIVTibv1wOKFeGekXlL8sumGEZxpeq911MpLlrndOKmOo5Ibi3JD8fbUOsE9A/5spj4B2KQNjhbplH+Cp26oEikjuNAA6cA/b2+/TMoC3i0klAYpVopBBV3FFna0gLP+q6t6fzG2v9TJrvmmVav6KVX6ylPNvD/LoReCjrkpgLIQuAQ6dSQNor9uV+EVt4plKhhkiS28DlYdgmTvNb5g4dzOhs8hoWty72J765VYWEDDC8qXn6N9GyrhsC3dhjASGn+1QDSCADYdbG9nrRlb4CZhrfcgOnHhAFva363kshg9HtCphigMgQy2oZXk4nLWK90/HuaPPkVj+N/lpIYjtiHOunToZJfIb0MWzyVI+7+I7WR6n6XbhLCPMe/sPXHHQ3HhQhZZ9xv7CDx9IkYJQBcF3LC+9kzJRi4QT0UTqrxcO3ncgXwvholP8Vg2KKPqFcbuyLPzbvr/o8zIilvLUFAEoDPfTEwSAC4BCzaGkFQVWzhWkgw8Pe1ckOEYFkZ0VLBuCpEiz+x45sbBL1SnnO5xhpjmdc572ZyW7ZmAABw1VfiWhhBWg4WGSf8lLnDHhNA36Qon34pnME/xpJQtWoo7ZZkkzvzYP/oW88/0UIMWDSOYKz7MijXlbNUggwAwUhrLzXDuB71HUKfPreFubfUxbOpu+OtTcOQ=
|
||||
|
||||
matrix:
|
||||
include:
|
||||
# Android
|
||||
- env: TARGET=arm-linux-androideabi DISABLE_TESTS=1
|
||||
|
||||
# Linux
|
||||
- env: TARGET=aarch64-unknown-linux-gnu
|
||||
- env: TARGET=arm-unknown-linux-gnueabi
|
||||
- env: TARGET=i686-unknown-linux-gnu
|
||||
- env: TARGET=x86_64-unknown-linux-gnu
|
||||
- env: TARGET=x86_64-unknown-linux-musl
|
||||
|
||||
# Mac
|
||||
- env: TARGET=i686-apple-darwin
|
||||
os: osx
|
||||
- env: TARGET=x86_64-apple-darwin
|
||||
os: osx
|
||||
|
||||
# BSD
|
||||
- env: TARGET=i686-unknown-freebsd DISABLE_TESTS=1
|
||||
- env: TARGET=x86_64-unknown-freebsd DISABLE_TESTS=1
|
||||
- env: TARGET=x86_64-unknown-netbsd DISABLE_TESTS=1
|
||||
|
||||
# Other channels
|
||||
- env: TARGET=x86_64-unknown-linux-gnu
|
||||
rust: beta
|
||||
- env: TARGET=x86_64-apple-darwin
|
||||
os: osx
|
||||
rust: beta
|
||||
- env: TARGET=x86_64-unknown-linux-gnu
|
||||
rust: nightly
|
||||
- env: TARGET=x86_64-apple-darwin
|
||||
os: osx
|
||||
rust: nightly
|
||||
|
||||
before_install:
|
||||
- set -e
|
||||
- rustup self update
|
||||
|
||||
install:
|
||||
- sh ci/install.sh
|
||||
- source ~/.cargo/env || true
|
||||
|
||||
script:
|
||||
- bash ci/script.sh
|
||||
|
||||
after_success:
|
||||
- bash ci/github_pages.sh
|
||||
|
||||
before_deploy:
|
||||
- sh ci/before_deploy.sh
|
||||
|
||||
deploy:
|
||||
api_key:
|
||||
- secure: cURRWBr034iqBz/ifD7uOunBfNR30YxIXfgLX0osWz+iafkVbhDGYYz9sBmRraqO2P7L2koEXMADVb/md1kI2+ykiq/ml+l9zuEAZPVmvSGUN7ZD+7s+lu3l5OBPG5z175T+b2q2q2m8XVR7TW20ra4QbE0bq06KAoOyjSgQVBTSCYsL9uTsGwiVRMEqqJT/BmKhKJNkpGsTKyBSKkOXvfeAAbE260vXUDEN9TYdJ3fvteRrpwLX56ee64gIZUq0RjDc4SKIEqilM6iUtNMvurqaewYNGkiXKRruV6BPCHxEHo6NNT46kOJLBJTf7gZw//dWhSoWpg9P0gdAnPWm407kSa3F7aJ1eRShAFQ4BLyfz9efTqm+jP3fOp7Mm7igSh9w6caSRuOnSsUf5+raRQ8E5Y9HsWGzzpZQk24Fx9EGZ04EeDSdpZAFz+jcbMpHf8t2p4CEx0CCNwYvKx6EydMKbMF5QteQ8SQkXNLhv7Rz2OgtXWYZPRVCMfQfOplsi2InsLCrQxTgwh+6u654SqVSgaHG+IncEAxBrdWy4rHcg7qereUcKfcY3k96vaDxdn/T2c00Ig0aNFR91YnixGMd6J6tQgDcRK9jh6fUm1CCBE9hT+pNUmtgYKuWBoLZexUZFFnfuBed0WciBot1bGDDamndqKq0jJiAzg+GMHk=
|
||||
file_glob: true
|
||||
file: "$CRATE_NAME-$TRAVIS_TAG-$TARGET.*"
|
||||
on:
|
||||
condition: "$TRAVIS_RUST_VERSION = stable"
|
||||
tags: true
|
||||
provider: releases
|
||||
skip_cleanup: true
|
||||
|
||||
branches:
|
||||
only:
|
||||
- "/^v\\d+\\.\\d+\\.\\d+.*$/"
|
||||
- master
|
||||
|
||||
notifications:
|
||||
email:
|
||||
on_success: never
|
||||
646
CHANGELOG.md
Normal file
646
CHANGELOG.md
Normal file
@@ -0,0 +1,646 @@
|
||||
# Changelog
|
||||
|
||||
## mdBook 0.4.10
|
||||
[2f7293a...dc2062a](https://github.com/rust-lang/mdBook/compare/2f7293a...dc2062a)
|
||||
|
||||
### Changed
|
||||
- Reverted breaking change in 0.4.9 that removed the `__non_exhaustive` marker
|
||||
on the `Book` struct.
|
||||
[#1572](https://github.com/rust-lang/mdBook/pull/1572)
|
||||
- Updated handlebars to 4.0.
|
||||
[#1550](https://github.com/rust-lang/mdBook/pull/1550)
|
||||
- Removed the `chapter_begin` id on the print page's chapter separators.
|
||||
[#1541](https://github.com/rust-lang/mdBook/pull/1541)
|
||||
|
||||
## mdBook 0.4.9
|
||||
[7e01cf9...d325c60](https://github.com/rust-lang/mdBook/compare/7e01cf9...d325c60)
|
||||
|
||||
### Changed
|
||||
- Updated all dependencies and raised the minimum Rust version to 1.42.
|
||||
[#1528](https://github.com/rust-lang/mdBook/pull/1528)
|
||||
- Added more detail to error message when a preprocessor fails.
|
||||
[#1526](https://github.com/rust-lang/mdBook/pull/1526)
|
||||
- Set max-width of HTML video tags to 100% to match img tags.
|
||||
[#1542](https://github.com/rust-lang/mdBook/pull/1542)
|
||||
|
||||
### Fixed
|
||||
- Type errors when parsing `book.toml` are no longer ignored.
|
||||
[#1539](https://github.com/rust-lang/mdBook/pull/1539)
|
||||
- Better handling if `mdbook serve` fails to start the http server.
|
||||
[#1555](https://github.com/rust-lang/mdBook/pull/1555)
|
||||
- Fixed the path for `edit-url-template` if the book used a source directory
|
||||
other than `src`.
|
||||
[#1554](https://github.com/rust-lang/mdBook/pull/1554)
|
||||
|
||||
## mdBook 0.4.8
|
||||
[fcceee4...b592b10](https://github.com/rust-lang/mdBook/compare/fcceee4...b592b10)
|
||||
|
||||
### Added
|
||||
- Added the option `output.html.edit-url-template` which can be a URL which is
|
||||
linked on each page to direct the user to a site (such as GitHub) where the
|
||||
user can directly suggest an edit for the page they are currently reading.
|
||||
[#1506](https://github.com/rust-lang/mdBook/pull/1506)
|
||||
|
||||
### Changed
|
||||
- Printed output now includes a page break between chapters.
|
||||
[#1485](https://github.com/rust-lang/mdBook/pull/1485)
|
||||
|
||||
### Fixed
|
||||
- HTML, such as HTML comments, is now ignored if it appears above the title line
|
||||
in `SUMMARY.md`.
|
||||
[#1437](https://github.com/rust-lang/mdBook/pull/1437)
|
||||
|
||||
## mdBook 0.4.7
|
||||
[9a9eb01...c83bbd6](https://github.com/rust-lang/mdBook/compare/9a9eb01...c83bbd6)
|
||||
|
||||
### Changed
|
||||
- Updated shlex parser to fix a minor parsing issue (used by the
|
||||
preprocessor/backend custom command config).
|
||||
[#1471](https://github.com/rust-lang/mdBook/pull/1471)
|
||||
- Enhanced text contrast of `light` theme to improve accessibility.
|
||||
[#1470](https://github.com/rust-lang/mdBook/pull/1470)
|
||||
|
||||
### Fixed
|
||||
- Fixed some issues with fragment scrolling and linking.
|
||||
[#1463](https://github.com/rust-lang/mdBook/pull/1463)
|
||||
|
||||
## mdBook 0.4.6
|
||||
[eaa6914...1a0c296](https://github.com/rust-lang/mdBook/compare/eaa6914...1a0c296)
|
||||
|
||||
### Changed
|
||||
- The chapter name is now included in the search breadcrumbs.
|
||||
[#1389](https://github.com/rust-lang/mdBook/pull/1389)
|
||||
- Pressing Escape will remove the `?highlight` argument from the URL.
|
||||
[#1427](https://github.com/rust-lang/mdBook/pull/1427)
|
||||
- `mdbook init --theme` will now place the theme in the root of the book
|
||||
directory instead of in the `src` directory.
|
||||
[#1432](https://github.com/rust-lang/mdBook/pull/1432)
|
||||
- A custom renderer that sets the `command` to a relative path now interprets
|
||||
the relative path relative to the book root. Previously it was inconsistent
|
||||
based on the platform (either relative to the current directory, or relative
|
||||
to the renderer output directory). Paths relative to the output directory
|
||||
are still supported with a deprecation warning.
|
||||
[#1418](https://github.com/rust-lang/mdBook/pull/1418)
|
||||
- The `theme` directory in the config is now interpreted as relative to the
|
||||
book root, instead of the current directory.
|
||||
[#1405](https://github.com/rust-lang/mdBook/pull/1405)
|
||||
- Handle UTF-8 BOM for chapter sources.
|
||||
[#1285](https://github.com/rust-lang/mdBook/pull/1285)
|
||||
- Removed extra whitespace added to `{{#playground}}` snippets.
|
||||
[#1375](https://github.com/rust-lang/mdBook/pull/1375)
|
||||
|
||||
### Fixed
|
||||
- Clicking on a search result with multiple search words will now correctly
|
||||
highlight all of the words.
|
||||
[#1426](https://github.com/rust-lang/mdBook/pull/1426)
|
||||
- Properly handle `<` and `>` characters in the table of contents.
|
||||
[#1376](https://github.com/rust-lang/mdBook/pull/1376)
|
||||
- Fixed to properly serialize the `build` table in the config, which prevented
|
||||
setting it in the API.
|
||||
[#1378](https://github.com/rust-lang/mdBook/pull/1378)
|
||||
|
||||
## mdBook 0.4.5
|
||||
[eaa6914...f66df09](https://github.com/rust-lang/mdBook/compare/eaa6914...f66df09)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed XSS in the search page.
|
||||
[CVE-2020-26297](https://groups.google.com/g/rustlang-security-announcements/c/3-sO6of29O0)
|
||||
[648c9ae](https://github.com/rust-lang/mdBook/commit/648c9ae772bec83f0a5954d17b4287d5bb1d6606)
|
||||
|
||||
## mdBook 0.4.4
|
||||
[4df9ec9...01836ba](https://github.com/rust-lang/mdBook/compare/4df9ec9...01836ba)
|
||||
|
||||
### Added
|
||||
- Added the `output.html.print.enable` configuration value to disable the
|
||||
"print" page.
|
||||
[#1169](https://github.com/rust-lang/mdBook/pull/1169)
|
||||
- Added a list of supported languages for syntax-highlighting to the
|
||||
documentation.
|
||||
[#1345](https://github.com/rust-lang/mdBook/pull/1345)
|
||||
|
||||
### Fixed
|
||||
- Now supports symbolic links for files in the `src` directory.
|
||||
[#1323](https://github.com/rust-lang/mdBook/pull/1323)
|
||||
|
||||
## mdBook 0.4.3
|
||||
[9278b83...4df9ec9](https://github.com/rust-lang/mdBook/compare/9278b83...4df9ec9)
|
||||
|
||||
### Added
|
||||
- Added `output.html.cname` option to emit a `CNAME` file which is used by
|
||||
GitHub Pages to know which domain is being used.
|
||||
[#1311](https://github.com/rust-lang/mdBook/pull/1311)
|
||||
|
||||
### Changed
|
||||
- `mdbook test` no longer stops on the first test failure, but instead will
|
||||
run all the tests.
|
||||
[#1313](https://github.com/rust-lang/mdBook/pull/1313)
|
||||
- Removed the `local` font source for Source Code Pro, as the locally
|
||||
installed font may not render properly on FireFox on macOS.
|
||||
[#1307](https://github.com/rust-lang/mdBook/pull/1307)
|
||||
|
||||
### Fixed
|
||||
- Added newline to end of `.nojekyll` file.
|
||||
[#1310](https://github.com/rust-lang/mdBook/pull/1310)
|
||||
- Fixed missing space before draft chapter titles.
|
||||
[#1309](https://github.com/rust-lang/mdBook/pull/1309)
|
||||
|
||||
## mdBook 0.4.2
|
||||
[649f355...9278b83](https://github.com/rust-lang/mdBook/compare/649f355...9278b83)
|
||||
|
||||
### Changed
|
||||
- The "show hidden lines" icon has changed from the "expand" icon to an "eye".
|
||||
[#1281](https://github.com/rust-lang/mdBook/pull/1281)
|
||||
- Updated highlight.js. This adds several languages: c, c-like (effectively
|
||||
cpp), csharp (replaces cs), kotlin, less, lua, php-template, plaintext,
|
||||
python-repl, r, scss, typescript.
|
||||
[#1277](https://github.com/rust-lang/mdBook/pull/1277)
|
||||
|
||||
### Fixed
|
||||
- Fixed SUMMARY links that contained newlines.
|
||||
[#1291](https://github.com/rust-lang/mdBook/pull/1291)
|
||||
- Fixed SUMMARY links that contain `%20` spaces.
|
||||
[#1293](https://github.com/rust-lang/mdBook/pull/1293)
|
||||
- Fixed favicon so that if only the png or svg is overridden, the other is not
|
||||
automatically included in the `<link>` tag.
|
||||
[#1272](https://github.com/rust-lang/mdBook/pull/1272)
|
||||
|
||||
## mdBook 0.4.1
|
||||
[d4df7e7...649f355](https://github.com/rust-lang/mdBook/compare/d4df7e7...649f355)
|
||||
|
||||
### Changed
|
||||
- Removed several outdated dev-dependencies.
|
||||
[#1267](https://github.com/rust-lang/mdBook/pull/1267)
|
||||
|
||||
### Fixed
|
||||
- Fixed sidebar scrolling if the book includes part titles.
|
||||
[#1265](https://github.com/rust-lang/mdBook/pull/1265)
|
||||
- Don't include the default favicon if only one of the PNG or SVG is overridden.
|
||||
[#1266](https://github.com/rust-lang/mdBook/pull/1266)
|
||||
|
||||
## mdBook 0.4.0
|
||||
[99ecd4f...d4df7e7](https://github.com/rust-lang/mdBook/compare/99ecd4f...d4df7e7)
|
||||
|
||||
### Breaking Changes
|
||||
- Several of the changes in the release have altered the public API of the
|
||||
mdbook library.
|
||||
- Many dependencies have been updated or replaced.
|
||||
This also removes the `--websocket-hostname` and `--websocket-port` from
|
||||
the `serve` command.
|
||||
[#1211](https://github.com/rust-lang/mdBook/pull/1211)
|
||||
- A new "404" page is now automatically rendered. This requires knowledge of
|
||||
the base URL of your site to work properly. If you decide to use this as
|
||||
your 404 page, you should set the `site-url` setting in the book
|
||||
configuration so mdbook can generate the links correctly. Alternatively you
|
||||
can disable the 404 page generation, or set up your own 404 handling in your
|
||||
web server.
|
||||
[#1221](https://github.com/rust-lang/mdBook/pull/1221)
|
||||
- The `debug` and `output` features have been removed as they were unused.
|
||||
[#1211](https://github.com/rust-lang/mdBook/pull/1211)
|
||||
- If you are using customized themes, you may want to consider setting the
|
||||
`preferred-dark-theme` config setting, as it now defaults to "navy".
|
||||
[#1199](https://github.com/rust-lang/mdBook/pull/1199)
|
||||
- "Playpen" has been renamed to "playground". This is generally backwards
|
||||
compatible for users, but `{{#playpen}}` will now display warnings. This may
|
||||
impact books that have modified the "playpen" elements in the theme.
|
||||
[#1241](https://github.com/rust-lang/mdBook/pull/1241)
|
||||
- If a renderer is not installed, it is now treated as an error. If you want
|
||||
the old behavior of ignoring missing renderers, set the `optional` setting
|
||||
for that renderer.
|
||||
[#1122](https://github.com/rust-lang/mdBook/pull/1122)
|
||||
- If you have a custom favicon, you may need to look into adding an SVG
|
||||
version, otherwise the default SVG icon will be displayed.
|
||||
[#1230](https://github.com/rust-lang/mdBook/pull/1230)
|
||||
|
||||
### Added
|
||||
- Added a new `[rust]` configuration section to `book.toml`, which allows
|
||||
setting the default edition with `edition = "2018"`.
|
||||
[#1163](https://github.com/rust-lang/mdBook/pull/1163)
|
||||
- Renderers can now be marked as `optional`, so that they will be ignored if
|
||||
the renderer is not installed.
|
||||
[#1122](https://github.com/rust-lang/mdBook/pull/1122)
|
||||
- Added `head.hbs` to allow adding content to the `<head>` section in HTML.
|
||||
[#1206](https://github.com/rust-lang/mdBook/pull/1206)
|
||||
- Added "draft chapters". These are chapters listed without a link to indicate
|
||||
content yet to be written.
|
||||
[#1153](https://github.com/rust-lang/mdBook/pull/1153)
|
||||
- Added "parts" to split a book into different sections. Headers can be added
|
||||
to `SUMMARY.md` to signify different sections.
|
||||
[#1171](https://github.com/rust-lang/mdBook/pull/1171)
|
||||
- Added generation of a "404" page for handling missing pages and broken links.
|
||||
[#1221](https://github.com/rust-lang/mdBook/pull/1221)
|
||||
- Added configuration section for specifying URL redirects.
|
||||
[#1237](https://github.com/rust-lang/mdBook/pull/1237)
|
||||
- Added an SVG favicon that works with light and dark colors schemes.
|
||||
[#1230](https://github.com/rust-lang/mdBook/pull/1230)
|
||||
|
||||
### Changed
|
||||
- Changed default Rust attribute of `allow(unused_variables)` to `allow(unused)`.
|
||||
[#1195](https://github.com/rust-lang/mdBook/pull/1195)
|
||||
- Fonts are now served locally instead of from the Google Fonts CDN. The
|
||||
`copy-fonts` option was added to disable this if you want to supply your own
|
||||
fonts.
|
||||
[#1188](https://github.com/rust-lang/mdBook/pull/1188)
|
||||
- Switched the built-in webserver for the `serve` command to a new
|
||||
implementation. This results in some internal differences in how websockets
|
||||
are handled, which removes the separate websocket options. This should also
|
||||
make it easier to serve multiple books at once.
|
||||
[#1211](https://github.com/rust-lang/mdBook/pull/1211)
|
||||
- The default dark theme is now "navy".
|
||||
[#1199](https://github.com/rust-lang/mdBook/pull/1199)
|
||||
- "Playpen" has been renamed to "playground", matching the actual name of the
|
||||
service which was renamed many years ago.
|
||||
[#1241](https://github.com/rust-lang/mdBook/pull/1241)
|
||||
|
||||
### Fixed
|
||||
- Links with the `+` symbol should now work.
|
||||
[#1208](https://github.com/rust-lang/mdBook/pull/1208)
|
||||
- The `MDBOOK_BOOK` environment variable now correctly allows overriding the
|
||||
entire book configuration.
|
||||
[#1207](https://github.com/rust-lang/mdBook/pull/1207)
|
||||
- The sidebar can no longer be dragged outside of the window.
|
||||
[#1229](https://github.com/rust-lang/mdBook/pull/1229)
|
||||
- Hide the Rust Playground "play" button for `no_run` code samples.
|
||||
[#1249](https://github.com/rust-lang/mdBook/pull/1249)
|
||||
- Fixed the `--dest-dir` command-line option for the `serve` and `watch`
|
||||
commands.
|
||||
[#1228](https://github.com/rust-lang/mdBook/pull/1228)
|
||||
- Hotkey handlers are now disabled in `text` input fields (for example, typing
|
||||
`S` in a custom text input field).
|
||||
[#1244](https://github.com/rust-lang/mdBook/pull/1244)
|
||||
|
||||
## mdBook 0.3.7
|
||||
[88684d8...99ecd4f](https://github.com/rust-lang/mdBook/compare/88684d8...99ecd4f)
|
||||
|
||||
### Changed
|
||||
- Code spans in headers are no longer highlighted as code.
|
||||
[#1162](https://github.com/rust-lang/mdBook/pull/1162)
|
||||
- The sidebar will now scroll the activate page to the middle instead of the top.
|
||||
[#1161](https://github.com/rust-lang/mdBook/pull/1161)
|
||||
- Reverted change to reject build output within the `src` directory, and
|
||||
instead add a check that prevents infinite copies.
|
||||
[#1181](https://github.com/rust-lang/mdBook/pull/1181)
|
||||
[#1026](https://github.com/rust-lang/mdBook/pull/1026)
|
||||
|
||||
### Fixed
|
||||
- Fixed sidebar line-height jumping for collapsed chapters.
|
||||
[#1182](https://github.com/rust-lang/mdBook/pull/1182)
|
||||
- Fixed theme selector focus.
|
||||
[#1170](https://github.com/rust-lang/mdBook/pull/1170)
|
||||
|
||||
## mdBook 0.3.6
|
||||
[efdb832...88684d8](https://github.com/rust-lang/mdBook/compare/efdb832...88684d8)
|
||||
|
||||
### Added
|
||||
- `MDBook::execute_build_process` is now publicly accessible in the API so
|
||||
that plugins can more easily initiate the build process.
|
||||
[#1099](https://github.com/rust-lang/mdBook/pull/1099)
|
||||
|
||||
### Changed
|
||||
- Use a different color for Ayu theme's highlighting for Rust attributes (uses
|
||||
a bright color instead of the comment color).
|
||||
[#1133](https://github.com/rust-lang/mdBook/pull/1133)
|
||||
- Adjusted spacing of sidebar entries.
|
||||
[#1137](https://github.com/rust-lang/mdBook/pull/1137)
|
||||
- Slightly adjusted line-height of `<p>`, `<ul>`, and `<ol>`.
|
||||
[#1136](https://github.com/rust-lang/mdBook/pull/1136)
|
||||
- Handlebars updated to 3.0.
|
||||
[#1130](https://github.com/rust-lang/mdBook/pull/1130)
|
||||
|
||||
### Fixed
|
||||
- Fix an issue with sidebar scroll position on reload.
|
||||
[#1108](https://github.com/rust-lang/mdBook/pull/1108)
|
||||
- `mdbook serve` will retain the current scroll position when the page is reloaded.
|
||||
[#1097](https://github.com/rust-lang/mdBook/pull/1097)
|
||||
- Fixed the page name if the book didn't have a title to not be prefixed with ` - `.
|
||||
[#1145](https://github.com/rust-lang/mdBook/pull/1145)
|
||||
- HTML attributes `rel=next` and `rel=previous` are now supported in "wide"
|
||||
mode (previously they were only set in narrow mode).
|
||||
[#1150](https://github.com/rust-lang/mdBook/pull/1150)
|
||||
- Prevent recursive copies when the destination directory is contained in the
|
||||
source directory.
|
||||
[#1135](https://github.com/rust-lang/mdBook/pull/1135)
|
||||
- Adjusted the menu bar animation to not immediately obscure the top content.
|
||||
[#989](https://github.com/rust-lang/mdBook/pull/989)
|
||||
- Fix for comments in SUMMARY.md that appear between items.
|
||||
[#1167](https://github.com/rust-lang/mdBook/pull/1167)
|
||||
|
||||
## mdBook 0.3.5
|
||||
[6e0d0fa...efdb832](https://github.com/rust-lang/mdBook/compare/6e0d0fa...efdb832)
|
||||
|
||||
### Changed
|
||||
- The `default-theme` config setting is now case-insensitive.
|
||||
[#1079](https://github.com/rust-lang/mdBook/pull/1079)
|
||||
|
||||
### Fixed
|
||||
- Fixed `#` hidden Rust code lines not rendering properly.
|
||||
[#1088](https://github.com/rust-lang/mdBook/pull/1088)
|
||||
- Updated pulldown-cmark to 0.6.1, fixing several issues.
|
||||
[#1021](https://github.com/rust-lang/mdBook/pull/1021)
|
||||
|
||||
## mdBook 0.3.4
|
||||
[e5f77aa...6e0d0fa](https://github.com/rust-lang/mdBook/compare/e5f77aa...6e0d0fa)
|
||||
|
||||
### Changed
|
||||
- Switch to relative `rem` font sizes from `px`.
|
||||
[#894](https://github.com/rust-lang/mdBook/pull/894)
|
||||
- Migrated repository to https://github.com/rust-lang/mdBook/
|
||||
[#1083](https://github.com/rust-lang/mdBook/pull/1083)
|
||||
|
||||
## mdBook 0.3.3
|
||||
[2b649fe...e5f77aa](https://github.com/rust-lang/mdBook/compare/2b649fe...e5f77aa)
|
||||
|
||||
### Changed
|
||||
- Improvements to the automatic dark theme selection.
|
||||
[#1069](https://github.com/rust-lang/mdBook/pull/1069)
|
||||
- Fragment links now prevent scrolling the header behind the menu bar.
|
||||
[#1077](https://github.com/rust-lang/mdBook/pull/1077)
|
||||
|
||||
### Fixed
|
||||
- Fixed error when building a book that has a spacer immediately after the
|
||||
first chapter.
|
||||
[#1075](https://github.com/rust-lang/mdBook/pull/1075)
|
||||
|
||||
## mdBook 0.3.2
|
||||
[9cd47eb...2b649fe](https://github.com/rust-lang/mdBook/compare/9cd47eb...2b649fe)
|
||||
|
||||
### Added
|
||||
- Added a markdown renderer, which is off by default. This may be useful for
|
||||
debugging preprocessors.
|
||||
[#1018](https://github.com/rust-lang/mdBook/pull/1018)
|
||||
- Code samples may now include line numbers with the
|
||||
`output.html.playpen.line-numbers` configuration value.
|
||||
[#1035](https://github.com/rust-lang/mdBook/pull/1035)
|
||||
- The `watch` and `serve` commands will now ignore files listed in
|
||||
`.gitignore`.
|
||||
[#1044](https://github.com/rust-lang/mdBook/pull/1044)
|
||||
- Added automatic dark-theme detection based on the CSS `prefers-color-scheme`
|
||||
feature. This may be enabled by setting `output.html.preferred-dark-theme`
|
||||
to your preferred dark theme.
|
||||
[#1037](https://github.com/rust-lang/mdBook/pull/1037)
|
||||
- Added `rustdoc_include` preprocessor. This makes it easier to include
|
||||
portions of an external Rust source file. The rest of the file is hidden,
|
||||
but the user may expand it to see the entire file, and will continue to work
|
||||
with `mdbook test`.
|
||||
[#1003](https://github.com/rust-lang/mdBook/pull/1003)
|
||||
- Added Ctrl-Enter shortcut to the playpen editor to automatically run the
|
||||
sample.
|
||||
[#1066](https://github.com/rust-lang/mdBook/pull/1066)
|
||||
- Added `output.html.playpen.copyable` configuration option to disable
|
||||
the copy button.
|
||||
[#1050](https://github.com/rust-lang/mdBook/pull/1050)
|
||||
- Added ability to dynamically expand and fold sections within the sidebar.
|
||||
See the `output.html.fold` configuration to enable this feature.
|
||||
[#1027](https://github.com/rust-lang/mdBook/pull/1027)
|
||||
|
||||
### Changed
|
||||
- Use standard `scrollbar-color` CSS along with webkit extension
|
||||
[#816](https://github.com/rust-lang/mdBook/pull/816)
|
||||
- The renderer build directory is no longer deleted before the renderer is
|
||||
run. This allows a backend to cache results between runs.
|
||||
[#985](https://github.com/rust-lang/mdBook/pull/985)
|
||||
- Next/prev links now highlight on hover to indicate it is clickable.
|
||||
[#994](https://github.com/rust-lang/mdBook/pull/994)
|
||||
- Increase padding of table headers.
|
||||
[#824](https://github.com/rust-lang/mdBook/pull/824)
|
||||
- Errors in `[output.html]` config are no longer ignored.
|
||||
[#1033](https://github.com/rust-lang/mdBook/pull/1033)
|
||||
- Updated highlight.js for syntax highlighting updates (primarily to add
|
||||
async/await to Rust highlighting).
|
||||
[#1041](https://github.com/rust-lang/mdBook/pull/1041)
|
||||
- Raised minimum supported rust version to 1.35.
|
||||
[#1003](https://github.com/rust-lang/mdBook/pull/1003)
|
||||
- Hidden code lines are no longer dynamically removed via JavaScript, but
|
||||
instead managed with CSS.
|
||||
[#846](https://github.com/rust-lang/mdBook/pull/846)
|
||||
[#1065](https://github.com/rust-lang/mdBook/pull/1065)
|
||||
- Changed the default font set for the ACE editor, giving preference to
|
||||
"Source Code Pro".
|
||||
[#1062](https://github.com/rust-lang/mdBook/pull/1062)
|
||||
- Windows 32-bit releases are no longer published.
|
||||
[#1071](https://github.com/rust-lang/mdBook/pull/1071)
|
||||
|
||||
### Fixed
|
||||
- Fixed sidebar auto-scrolling.
|
||||
[#1052](https://github.com/rust-lang/mdBook/pull/1052)
|
||||
- Fixed error message when running `clean` multiple times.
|
||||
[#1055](https://github.com/rust-lang/mdBook/pull/1055)
|
||||
- Actually fix the "next" link on index.html. The previous fix didn't work.
|
||||
[#1005](https://github.com/rust-lang/mdBook/pull/1005)
|
||||
- Stop using `inline-block` for `inline code`, fixing selection highlighting
|
||||
and some rendering issues.
|
||||
[#1058](https://github.com/rust-lang/mdBook/pull/1058)
|
||||
- Fix header auto-hide on browsers with momentum scrolling that allows
|
||||
negative `scrollTop`.
|
||||
[#1070](https://github.com/rust-lang/mdBook/pull/1070)
|
||||
|
||||
## mdBook 0.3.1
|
||||
[69a08ef...9cd47eb](https://github.com/rust-lang/mdBook/compare/69a08ef...9cd47eb)
|
||||
|
||||
### Added
|
||||
- 🔥 Added ability to include files using anchor points instead of line numbers.
|
||||
[#851](https://github.com/rust-lang/mdBook/pull/851)
|
||||
- Added `language` configuration value to set the language of the book, which
|
||||
will affect things like the `<html lang="en">` tag.
|
||||
[#941](https://github.com/rust-lang/mdBook/pull/941)
|
||||
|
||||
### Changed
|
||||
- Updated to handlebars 2.0.
|
||||
[#977](https://github.com/rust-lang/mdBook/pull/977)
|
||||
|
||||
### Fixed
|
||||
- Fixed memory leak warning.
|
||||
[#967](https://github.com/rust-lang/mdBook/pull/967)
|
||||
- Fix more print.html links.
|
||||
[#963](https://github.com/rust-lang/mdBook/pull/963)
|
||||
- Fixed crash on some unicode input.
|
||||
[#978](https://github.com/rust-lang/mdBook/pull/978)
|
||||
|
||||
## mdBook 0.3.0
|
||||
[6cbc41d...69a08ef](https://github.com/rust-lang/mdBook/compare/6cbc41d...69a08ef)
|
||||
|
||||
### Added
|
||||
- Added ability to resize the sidebar.
|
||||
[#849](https://github.com/rust-lang/mdBook/pull/849)
|
||||
- Added `load_with_config_and_summary` function to `MDBook` to be able to
|
||||
build a book with a custom `Summary`.
|
||||
[#883](https://github.com/rust-lang/mdBook/pull/883)
|
||||
- Set `noindex` on `print.html` page to prevent robots from indexing it.
|
||||
[#844](https://github.com/rust-lang/mdBook/pull/844)
|
||||
- Added support for ~~strikethrough~~ and GitHub-style tasklists.
|
||||
[#952](https://github.com/rust-lang/mdBook/pull/952)
|
||||
|
||||
### Changed
|
||||
- Command-line help output is now colored.
|
||||
[#861](https://github.com/rust-lang/mdBook/pull/861)
|
||||
- The build directory is now deleted before rendering starts, instead of after
|
||||
if finishes.
|
||||
[#878](https://github.com/rust-lang/mdBook/pull/878)
|
||||
- Removed dependency on `same-file` crate.
|
||||
[#903](https://github.com/rust-lang/mdBook/pull/903)
|
||||
- 💥 Renamed `with_preprecessor` to `with_preprocessor`.
|
||||
[#906](https://github.com/rust-lang/mdBook/pull/906)
|
||||
- Updated ACE editor to 1.4.4, should remove a JavaScript console warning.
|
||||
[#935](https://github.com/rust-lang/mdBook/pull/935)
|
||||
- Dependencies have been updated.
|
||||
[#934](https://github.com/rust-lang/mdBook/pull/934)
|
||||
[#945](https://github.com/rust-lang/mdBook/pull/945)
|
||||
- Highlight.js has been updated. This fixes some TOML highlighting, and adds
|
||||
Julia support.
|
||||
[#942](https://github.com/rust-lang/mdBook/pull/942)
|
||||
- 🔥 Updated to pulldown-cmark 0.5. This may have significant changes to the
|
||||
formatting of existing books, as the newer version has more accurate
|
||||
interpretation of the CommonMark spec and a large number of bug fixes and
|
||||
changes.
|
||||
[#898](https://github.com/rust-lang/mdBook/pull/898)
|
||||
- The `diff` language should now highlight correctly.
|
||||
[#943](https://github.com/rust-lang/mdBook/pull/943)
|
||||
- Make the blank region of a header not clickable.
|
||||
[#948](https://github.com/rust-lang/mdBook/pull/948)
|
||||
- Rustdoc tests now use the preprocessed content instead of the raw,
|
||||
unpreprocessed content.
|
||||
[#891](https://github.com/rust-lang/mdBook/pull/891)
|
||||
|
||||
### Fixed
|
||||
- Fixed file change detection so that `mdbook serve` only reloads once when
|
||||
multiple files are changed at once.
|
||||
[#870](https://github.com/rust-lang/mdBook/pull/870)
|
||||
- Fixed on-hover color highlighting for links in sidebar.
|
||||
[#834](https://github.com/rust-lang/mdBook/pull/834)
|
||||
- Fixed loss of focus when clicking the "Copy" button in code blocks.
|
||||
[#867](https://github.com/rust-lang/mdBook/pull/867)
|
||||
- Fixed incorrectly stripping the path for `additional-js` files.
|
||||
[#796](https://github.com/rust-lang/mdBook/pull/796)
|
||||
- Fixed color of `code spans` that are links.
|
||||
[#905](https://github.com/rust-lang/mdBook/pull/905)
|
||||
- Fixed "next" navigation on index.html.
|
||||
[#916](https://github.com/rust-lang/mdBook/pull/916)
|
||||
- Fixed keyboard chapter navigation for `file` urls.
|
||||
[#915](https://github.com/rust-lang/mdBook/pull/915)
|
||||
- Fixed bad wrapping for inline code on some browsers.
|
||||
[#818](https://github.com/rust-lang/mdBook/pull/818)
|
||||
- Properly load an existing `SUMMARY.md` in `mdbook init`.
|
||||
[#841](https://github.com/rust-lang/mdBook/pull/841)
|
||||
- Fixed some broken links in `print.html`.
|
||||
[#871](https://github.com/rust-lang/mdBook/pull/871)
|
||||
- The Rust Playground link now supports the 2018 edition.
|
||||
[#946](https://github.com/rust-lang/mdBook/pull/946)
|
||||
|
||||
## mdBook 0.2.3 (2018-01-18)
|
||||
[2c20c99...6cbc41d](https://github.com/rust-lang/mdBook/compare/2c20c99...6cbc41d)
|
||||
|
||||
### Added
|
||||
- Added an optional button to the top of the page which will link to a git
|
||||
repository. Use the `git-repository-url` and `git-repository-icon` options
|
||||
in the `[output.html]` section to enable it and set its appearance.
|
||||
[#802](https://github.com/rust-lang/mdBook/pull/802)
|
||||
- Added a `default-theme` option to the `[output.html]` section.
|
||||
[#804](https://github.com/rust-lang/mdBook/pull/804)
|
||||
|
||||
### Changed
|
||||
- 💥 Header ID anchors no longer add an arbitrary `a` character for headers
|
||||
that start with a non-ascii-alphabetic character.
|
||||
[#788](https://github.com/rust-lang/mdBook/pull/788)
|
||||
|
||||
### Fixed
|
||||
- Fix websocket hostname usage
|
||||
[#865](https://github.com/rust-lang/mdBook/pull/865)
|
||||
- Fixing links in print.html
|
||||
[#866](https://github.com/rust-lang/mdBook/pull/866)
|
||||
|
||||
## mdBook 0.2.2 (2018-10-19)
|
||||
[7e2e095...2c20c99](https://github.com/rust-lang/mdBook/compare/7e2e095...2c20c99)
|
||||
|
||||
### Added
|
||||
- 🎉 Process-based custom preprocessors. See [the
|
||||
docs](https://rust-lang.github.io/mdBook/for_developers/preprocessors.html)
|
||||
for more.
|
||||
[#792](https://github.com/rust-lang/mdBook/pull/792)
|
||||
|
||||
- 🎉 Configurable preprocessors.
|
||||
|
||||
Added `build.use-default-preprocessors` boolean TOML key to allow disabling
|
||||
the built-in `links` and `index` preprocessors.
|
||||
|
||||
Added `[preprocessor]` TOML tables to configure each preprocessor.
|
||||
|
||||
Specifying `[preprocessor.links]` or `[preprocessor.index]` will enable the
|
||||
respective built-in preprocessor if `build.use-default-preprocessors` is
|
||||
`false`.
|
||||
|
||||
Added `fn supports_renderer(&self, renderer: &str) -> bool` to the
|
||||
`Preprocessor` trait to specify if the preprocessor supports the given
|
||||
renderer. The default implementation always returns `true`.
|
||||
|
||||
`Preprocessor::run` now takes a book by value instead of a mutable
|
||||
reference. It should return a `Book` value with the intended modifications.
|
||||
|
||||
Added `PreprocessorContext::renderer` to indicate the renderer being used.
|
||||
|
||||
[#658](https://github.com/rust-lang/mdBook/pull/658)
|
||||
[#787](https://github.com/rust-lang/mdBook/pull/787)
|
||||
|
||||
### Fixed
|
||||
- Fix paths to additional CSS and JavaScript files
|
||||
[#777](https://github.com/rust-lang/mdBook/pull/777)
|
||||
- Ensure section numbers are correctly incremented after a horizontal
|
||||
separator
|
||||
[#790](https://github.com/rust-lang/mdBook/pull/790)
|
||||
|
||||
## mdBook 0.2.1 (2018-08-22)
|
||||
[91ffca1...7e2e095](https://github.com/rust-lang/mdBook/compare/91ffca1...7e2e095)
|
||||
|
||||
### Changed
|
||||
- Update to handlebars-rs 1.0
|
||||
[#761](https://github.com/rust-lang/mdBook/pull/761)
|
||||
|
||||
### Fixed
|
||||
- Fix table colors, broken by Stylus -> CSS transition
|
||||
[#765](https://github.com/rust-lang/mdBook/pull/765)
|
||||
|
||||
## mdBook 0.2.0 (2018-08-02)
|
||||
|
||||
### Changed
|
||||
- 💥 This release changes how links are handled in mdBook. Previously, relative
|
||||
links were interpreted relative to the book's root. In `0.2.0`+ links are
|
||||
relative to the page they are in, and use the `.md` extension. This has [several
|
||||
advantages](https://github.com/rust-lang/mdBook/pull/603#issue-166701447),
|
||||
such as making links work in other markdown viewers like GitHub. You will
|
||||
likely have to change links in your book to accommodate this change. For
|
||||
example, a book with this layout:
|
||||
|
||||
```
|
||||
chapter_1/
|
||||
section_1.md
|
||||
section_2.md
|
||||
SUMMARY.md
|
||||
```
|
||||
|
||||
Previously a link in `section_1.md` to `section_2.md` would look like this:
|
||||
```markdown
|
||||
[section_2](chapter_1/section_2.html)
|
||||
```
|
||||
|
||||
Now it must be changed to this:
|
||||
```markdown
|
||||
[section_2](section_2.md)
|
||||
```
|
||||
|
||||
- 💥 `mdbook test --library-path` now accepts a comma-delimited list of
|
||||
arguments rather than taking all following arguments. This makes it easier
|
||||
to handle the trailing book directory argument without always needing to put
|
||||
` -- ` before it. Multiple instances of the option continue to be accepted:
|
||||
`mdbook test -L foo -L bar`.
|
||||
|
||||
- 💥 `mdbook serve` has some of its options renamed for clarity. See `mdbook
|
||||
help serve` for details.
|
||||
|
||||
- Embedded rust playpens now use the "stable" playground API.
|
||||
[#754](https://github.com/rust-lang/mdBook/pull/754)
|
||||
|
||||
### Fixed
|
||||
- Escaped includes (`\{{#include file.rs}}`) will now render correctly.
|
||||
[f30ce01](https://github.com/rust-lang/mdBook/commit/f30ce0184d71e342141145472bf816419d30a2c5)
|
||||
- `index.html` will now render correctly when the book's first section is
|
||||
inside a subdirectory.
|
||||
[#756](https://github.com/rust-lang/mdBook/pull/756)
|
||||
@@ -5,22 +5,22 @@ Welcome stranger!
|
||||
If you have come here to learn how to contribute to mdBook, we have some tips for you!
|
||||
|
||||
First of all, don't hesitate to ask questions!
|
||||
Use the [issue tracker](https://github.com/rust-lang-nursery/mdBook/issues), no question is too simple.
|
||||
Use the [issue tracker](https://github.com/rust-lang/mdBook/issues), no question is too simple.
|
||||
If we don't respond in a couple of days, ping us @Michael-F-Bryan, @budziq, @steveklabnik, @frewsxcv it might just be that we forgot. :wink:
|
||||
|
||||
### Issues to work on
|
||||
|
||||
Any issue is up for the grabbing, but if you are starting out, you might be interested in the
|
||||
[E-Easy issues](https://github.com/rust-lang-nursery/mdBook/issues?q=is%3Aopen+is%3Aissue+label%3AE-Easy).
|
||||
[E-Easy issues](https://github.com/rust-lang/mdBook/issues?q=is%3Aopen+is%3Aissue+label%3AE-Easy).
|
||||
Those are issues that are considered more straightforward for beginners to Rust or the codebase itself.
|
||||
These issues can be a good launching pad for more involved issues. Easy tasks for a first time contribution
|
||||
include documentation improvements, new tests, examples, updating dependencies, etc.
|
||||
|
||||
If you come from a web development background, you might be interested in issues related to web technologies tagged
|
||||
[A-JavaScript](https://github.com/rust-lang-nursery/mdBook/issues?q=is%3Aopen+is%3Aissue+label%3AA-JavaScript),
|
||||
[A-Style](https://github.com/rust-lang-nursery/mdBook/issues?q=is%3Aopen+is%3Aissue+label%3AA-Style),
|
||||
[A-HTML](https://github.com/rust-lang-nursery/mdBook/issues?q=is%3Aopen+is%3Aissue+label%3AA-HTML) or
|
||||
[A-Mobile](https://github.com/rust-lang-nursery/mdBook/issues?q=is%3Aopen+is%3Aissue+label%3AA-Mobile).
|
||||
[A-JavaScript](https://github.com/rust-lang/mdBook/issues?q=is%3Aopen+is%3Aissue+label%3AA-JavaScript),
|
||||
[A-Style](https://github.com/rust-lang/mdBook/issues?q=is%3Aopen+is%3Aissue+label%3AA-Style),
|
||||
[A-HTML](https://github.com/rust-lang/mdBook/issues?q=is%3Aopen+is%3Aissue+label%3AA-HTML) or
|
||||
[A-Mobile](https://github.com/rust-lang/mdBook/issues?q=is%3Aopen+is%3Aissue+label%3AA-Mobile).
|
||||
|
||||
When you decide you want to work on a specific issue, ping us on that issue so that we can assign it to you.
|
||||
Again, do not hesitate to ask questions. We will gladly mentor anyone that want to tackle an issue.
|
||||
@@ -41,36 +41,60 @@ mdBook builds on stable Rust, if you want to build mdBook from source, here are
|
||||
0. Clone this repository with git.
|
||||
|
||||
```
|
||||
git clone https://github.com/rust-lang-nursery/mdBook.git
|
||||
git clone https://github.com/rust-lang/mdBook.git
|
||||
```
|
||||
0. Navigate into the newly created `mdBook` directory
|
||||
0. Run `cargo build`
|
||||
|
||||
The resulting binary can be found in `mdBook/target/debug/` under the name `mdBook` or `mdBook.exe`.
|
||||
|
||||
### Code Quality
|
||||
|
||||
### Making changes to the style
|
||||
We love code quality and Rust has some excellent tools to assist you with contributions.
|
||||
|
||||
mdBook doesn't use CSS directly but uses [Stylus](http://stylus-lang.com/), a CSS-preprocessor which compiles to CSS.
|
||||
#### Formatting Code with rustfmt
|
||||
|
||||
When you want to change the style, it is important to not change the CSS directly because any manual modification to
|
||||
the CSS files will be overwritten when compiling the stylus files. Instead, you should make your changes directly in the
|
||||
[stylus files](https://github.com/rust-lang-nursery/mdBook/tree/master/src/theme/stylus) and regenerate the CSS.
|
||||
Before you make your Pull Request to the project, please run it through the `rustfmt` utility.
|
||||
This will ensure we have good quality source code that is better for us all to maintain.
|
||||
|
||||
For this to work, you first need [Node and NPM](https://nodejs.org/en/) installed on your machine.
|
||||
Then run the following command to install both [stylus](http://stylus-lang.com/) and [nib](https://tj.github.io/nib/), you might need `sudo` to install successfully.
|
||||
[rustfmt](https://github.com/rust-lang/rustfmt) has a lot more information on the project.
|
||||
The quick guide is
|
||||
|
||||
```
|
||||
npm install -g stylus nib
|
||||
```
|
||||
1. Install it
|
||||
```
|
||||
rustup component add rustfmt
|
||||
```
|
||||
1. You can now run `rustfmt` on a single file simply by...
|
||||
```
|
||||
rustfmt src/path/to/your/file.rs
|
||||
```
|
||||
... or you can format the entire project with
|
||||
```
|
||||
cargo fmt
|
||||
```
|
||||
When run through `cargo` it will format all bin and lib files in the current crate.
|
||||
|
||||
When that finished, you can simply regenerate the CSS files by building mdBook with the following command:
|
||||
For more information, such as running it from your favourite editor, please see the `rustfmt` project. [rustfmt](https://github.com/rust-lang/rustfmt)
|
||||
|
||||
```
|
||||
cargo build --features=regenerate-css
|
||||
```
|
||||
|
||||
This should automatically call the appropriate stylus command to recompile the files to CSS and include them in the project.
|
||||
#### Finding Issues with Clippy
|
||||
|
||||
Clippy is a code analyser/linter detecting mistakes, and therefore helps to improve your code.
|
||||
Like formatting your code with `rustfmt`, running clippy regularly and before your Pull Request will
|
||||
help us maintain awesome code.
|
||||
|
||||
The best documentation can be found over at [rust-clippy](https://github.com/rust-lang/rust-clippy)
|
||||
|
||||
1. To install
|
||||
```
|
||||
rustup component add clippy
|
||||
```
|
||||
2. Running clippy
|
||||
```
|
||||
cargo clippy
|
||||
```
|
||||
|
||||
Clippy has an ever growing list of checks, that are managed in [lint files](https://rust-lang.github.io/rust-clippy/master/index.html).
|
||||
|
||||
### Making a pull-request
|
||||
|
||||
@@ -78,7 +102,7 @@ When you feel comfortable that your changes could be integrated into mdBook, you
|
||||
One of the core maintainers will then approve the changes or request some changes before it gets merged.
|
||||
|
||||
If you want to make your pull-request even better, you might want to run [Clippy](https://github.com/Manishearth/rust-clippy)
|
||||
and [rustfmt](https://github.com/rust-lang-nursery/rustfmt) on the code first.
|
||||
and [rustfmt](https://github.com/rust-lang/rustfmt) on the code first.
|
||||
This is not a requirement though and will never block a pull-request from being merged.
|
||||
|
||||
That's it, happy contributions! :tada: :tada: :tada:
|
||||
|
||||
1959
Cargo.lock
generated
Normal file
1959
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
82
Cargo.toml
82
Cargo.toml
@@ -1,72 +1,64 @@
|
||||
[package]
|
||||
name = "mdbook"
|
||||
version = "0.1.2"
|
||||
authors = ["Mathieu David <mathieudavid@mathieudavid.org>", "Michael-F-Bryan <michaelfbryan@gmail.com>"]
|
||||
description = "create books from markdown files (like Gitbook)"
|
||||
documentation = "http://rust-lang-nursery.github.io/mdBook/index.html"
|
||||
repository = "https://github.com/rust-lang-nursery/mdBook"
|
||||
version = "0.4.10"
|
||||
authors = [
|
||||
"Mathieu David <mathieudavid@mathieudavid.org>",
|
||||
"Michael-F-Bryan <michaelfbryan@gmail.com>",
|
||||
"Matt Ickstadt <mattico8@gmail.com>"
|
||||
]
|
||||
documentation = "http://rust-lang.github.io/mdBook/index.html"
|
||||
edition = "2018"
|
||||
exclude = ["/guide/*"]
|
||||
keywords = ["book", "gitbook", "rustbook", "markdown"]
|
||||
license = "MPL-2.0"
|
||||
readme = "README.md"
|
||||
build = "build.rs"
|
||||
exclude = [
|
||||
"book-example/*",
|
||||
"src/theme/stylus/**",
|
||||
]
|
||||
|
||||
[package.metadata.release]
|
||||
sign-commit = true
|
||||
push-remote = "upstream"
|
||||
tag-prefix = "v"
|
||||
repository = "https://github.com/rust-lang/mdBook"
|
||||
description = "Creates a book from markdown files"
|
||||
|
||||
[dependencies]
|
||||
clap = "2.24"
|
||||
anyhow = "1.0.28"
|
||||
chrono = "0.4"
|
||||
handlebars = "0.29"
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
error-chain = "0.11"
|
||||
serde_json = "1.0"
|
||||
pulldown-cmark = "0.1"
|
||||
clap = "2.24"
|
||||
env_logger = "0.7.1"
|
||||
handlebars = "4.0"
|
||||
lazy_static = "1.0"
|
||||
log = "0.4"
|
||||
env_logger = "0.5.0-rc.1"
|
||||
toml = "0.4"
|
||||
memchr = "2.0"
|
||||
open = "1.1"
|
||||
regex = "0.2.1"
|
||||
tempdir = "0.3.4"
|
||||
itertools = "0.7"
|
||||
shlex = "0.1"
|
||||
toml-query = "0.6"
|
||||
pulldown-cmark = "0.7.0"
|
||||
regex = "1.0.0"
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
serde_json = "1.0"
|
||||
shlex = "1"
|
||||
tempfile = "3.0"
|
||||
toml = "0.5.1"
|
||||
|
||||
# Watch feature
|
||||
notify = { version = "4.0", optional = true }
|
||||
time = { version = "0.1.34", optional = true }
|
||||
crossbeam = { version = "0.3", optional = true }
|
||||
gitignore = { version = "1.0", optional = true }
|
||||
|
||||
# Serve feature
|
||||
iron = { version = "0.5", optional = true }
|
||||
staticfile = { version = "0.4", optional = true }
|
||||
ws = { version = "0.7", optional = true}
|
||||
futures-util = { version = "0.3.4", optional = true }
|
||||
tokio = { version = "0.2.18", features = ["macros"], optional = true }
|
||||
warp = { version = "0.2.2", default-features = false, features = ["websocket"], optional = true }
|
||||
|
||||
[build-dependencies]
|
||||
error-chain = "0.11"
|
||||
# Search feature
|
||||
elasticlunr-rs = { version = "2.3", optional = true, default-features = false }
|
||||
ammonia = { version = "3", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
select = "0.4"
|
||||
pretty_assertions = "0.4"
|
||||
select = "0.5"
|
||||
semver = "0.11.0"
|
||||
pretty_assertions = "0.6"
|
||||
walkdir = "2.0"
|
||||
|
||||
[features]
|
||||
default = ["output", "watch", "serve"]
|
||||
debug = []
|
||||
output = []
|
||||
regenerate-css = []
|
||||
watch = ["notify", "time", "crossbeam"]
|
||||
serve = ["iron", "staticfile", "ws"]
|
||||
default = ["watch", "serve", "search"]
|
||||
watch = ["notify", "gitignore"]
|
||||
serve = ["futures-util", "tokio", "warp"]
|
||||
search = ["elasticlunr-rs", "ammonia"]
|
||||
|
||||
[[bin]]
|
||||
doc = false
|
||||
name = "mdbook"
|
||||
path = "src/bin/mdbook.rs"
|
||||
|
||||
131
README.md
131
README.md
@@ -1,25 +1,8 @@
|
||||
# mdBook
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td><strong>Linux / OS X</strong></td>
|
||||
<td>
|
||||
<a href="https://travis-ci.org/rust-lang-nursery/mdBook"><img src="https://travis-ci.org/rust-lang-nursery/mdBook.svg?branch=master"></a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Windows</strong></td>
|
||||
<td>
|
||||
<a href="https://ci.appveyor.com/project/azerupi/mdbook/"><img src="https://ci.appveyor.com/api/projects/status/o38racsnbcospyc8/branch/master?svg=true"></a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<a href="https://crates.io/crates/mdbook"><img src="https://img.shields.io/crates/v/mdbook.svg"></a>
|
||||
<a href="LICENSE"><img src="https://img.shields.io/github/license/rust-lang-nursery/mdBook.svg"></a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
[](https://github.com/rust-lang/mdBook/actions?workflow=CI)
|
||||
[](https://crates.io/crates/mdbook)
|
||||
[](LICENSE)
|
||||
|
||||
mdBook is a utility to create modern online books from Markdown files.
|
||||
|
||||
@@ -40,8 +23,8 @@ There are multiple ways to install mdBook.
|
||||
path to the binary into your `PATH`.
|
||||
|
||||
2. **From Crates.io**
|
||||
j
|
||||
This requires [Rust] and Cargo to be installed. Once you have installed
|
||||
|
||||
This requires at least [Rust] 1.39 and Cargo to be installed. Once you have installed
|
||||
Rust, type the following in the terminal:
|
||||
|
||||
```
|
||||
@@ -57,33 +40,37 @@ j
|
||||
another CI server, we recommend that you specify a semver version range for
|
||||
mdBook when you install it through your script!
|
||||
|
||||
This will constrain the server to install the latests **non-breaking**
|
||||
This will constrain the server to install the latest **non-breaking**
|
||||
version of mdBook and will prevent your books from failing to build because
|
||||
we released a new version. For example:
|
||||
we released a new version.
|
||||
|
||||
You can also disable default features to speed up compile time.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
cargo install mdbook --vers "^0.1.0"
|
||||
cargo install mdbook --no-default-features --features output --vers "^0.1.0"
|
||||
```
|
||||
|
||||
3. **From Git**
|
||||
3. **From Git**
|
||||
|
||||
The version published to crates.io will ever so slightly be behind the
|
||||
version hosted here on GitHub. If you need the latest version you can build
|
||||
the git version of mdBook yourself. Cargo makes this ***super easy***!
|
||||
|
||||
```
|
||||
cargo install --git https://github.com/rust-lang-nursery/mdBook.git mdbook
|
||||
cargo install --git https://github.com/rust-lang/mdBook.git mdbook
|
||||
```
|
||||
|
||||
Again, make sure to add the Cargo bin directory to your `PATH`.
|
||||
|
||||
4. **For Contributions**
|
||||
4. **For Contributions**
|
||||
|
||||
If you want to contribute to mdBook you will have to clone the repository on
|
||||
your local machine:
|
||||
|
||||
```
|
||||
git clone https://github.com/rust-lang-nursery/mdBook.git
|
||||
git clone https://github.com/rust-lang/mdBook.git
|
||||
```
|
||||
|
||||
`cd` into `mdBook/` and run
|
||||
@@ -98,16 +85,17 @@ j
|
||||
|
||||
## Usage
|
||||
|
||||
mdBook will primarily be used as a command line tool, even though it exposes
|
||||
mdBook is primarily used as a command line tool, even though it exposes
|
||||
all its functionality as a Rust crate for integration in other projects.
|
||||
|
||||
Here are the main commands you will want to run. For a more exhaustive
|
||||
explanation, check out the [User Guide].
|
||||
|
||||
- `mdbook init`
|
||||
- `mdbook init <directory>`
|
||||
|
||||
The init command will create a directory with the minimal boilerplate to
|
||||
start with.
|
||||
start with. If the `<directory>` parameter is omitted, the current
|
||||
directory will be used.
|
||||
|
||||
```
|
||||
book-test/
|
||||
@@ -141,6 +129,65 @@ explanation, check out the [User Guide].
|
||||
`http://localhost:3000` (port is changeable) and reloads the browser when a
|
||||
change occurs.
|
||||
|
||||
- `mdbook clean`
|
||||
|
||||
Delete directory in which generated book is located.
|
||||
|
||||
### 3rd Party Plugins
|
||||
|
||||
The way a book is loaded and rendered can be configured by the user via third
|
||||
party plugins. These plugins are just programs which will be invoked during the
|
||||
build process and are split into roughly two categories, *preprocessors* and
|
||||
*renderers*.
|
||||
|
||||
Preprocessors are used to transform a book before it is sent to a renderer.
|
||||
One example would be to replace all occurrences of
|
||||
`{{#include some_file.ext}}` with the contents of that file. Some existing
|
||||
preprocessors are:
|
||||
|
||||
- `index` - a built-in preprocessor (enabled by default) which will transform
|
||||
all `README.md` chapters to `index.md` so `foo/README.md` can be accessed via
|
||||
the url `foo/` when published to a browser
|
||||
- `links` - a built-in preprocessor (enabled by default) for expanding the
|
||||
`{{# playground}}` and `{{# include}}` helpers in a chapter.
|
||||
- [`katex`](https://github.com/lzanini/mdbook-katex) - a preprocessor rendering LaTex equations to HTML.
|
||||
|
||||
Renderers are given the final book so they can do something with it. This is
|
||||
typically used for, as the name suggests, rendering the document in a particular
|
||||
format, however there's nothing stopping a renderer from doing static analysis
|
||||
of a book in order to validate links or run tests. Some existing renderers are:
|
||||
|
||||
- `html` - the built-in renderer which will generate a HTML version of the book
|
||||
- `markdown` - the built-in renderer (disabled by default) which will run
|
||||
preprocessors then output the resulting Markdown. Useful for debugging
|
||||
preprocessors.
|
||||
- [`linkcheck`] - a backend which will check that all links are valid
|
||||
- [`epub`] - an experimental EPUB generator
|
||||
|
||||
> **Note for Developers:** Feel free to send us a PR if you've developed your
|
||||
> own plugin and want it mentioned here.
|
||||
|
||||
A preprocessor or renderer is enabled by installing the appropriate program and
|
||||
then mentioning it in the book's `book.toml` file.
|
||||
|
||||
```console
|
||||
$ cargo install mdbook-linkcheck
|
||||
$ edit book.toml && cat book.toml
|
||||
[book]
|
||||
title = "My Awesome Book"
|
||||
authors = ["Michael-F-Bryan"]
|
||||
|
||||
[output.html]
|
||||
|
||||
[output.linkcheck] # enable the "mdbook-linkcheck" renderer
|
||||
|
||||
$ mdbook build
|
||||
2018-10-20 13:57:51 [INFO] (mdbook::book): Book building has started
|
||||
2018-10-20 13:57:51 [INFO] (mdbook::book): Running the html backend
|
||||
2018-10-20 13:57:53 [INFO] (mdbook::book): Running the linkcheck backend
|
||||
```
|
||||
|
||||
For more information on the plugin system, consult the [User Guide].
|
||||
|
||||
### As a library
|
||||
|
||||
@@ -159,12 +206,12 @@ Contributions are highly appreciated and encouraged! Don't hesitate to
|
||||
participate to discussions in the issues, propose new features and ask for
|
||||
help.
|
||||
|
||||
If you are just starting out with Rust, there are a series of issus that are
|
||||
If you are just starting out with Rust, there are a series of issues that are
|
||||
tagged [E-Easy] and **we will gladly mentor you** so that you can successfully
|
||||
go through the process of fixing a bug or adding a new feature! Let us know if
|
||||
you need any help.
|
||||
|
||||
For more info about contributing, check out our [contribution guide] who helps
|
||||
For more info about contributing, check out our [contribution guide] which helps
|
||||
you go through the build and contribution process!
|
||||
|
||||
There is also a [rendered version][master-docs] of the latest API docs
|
||||
@@ -176,12 +223,14 @@ available, for those hacking on `master`.
|
||||
All the code in this repository is released under the ***Mozilla Public License v2.0***, for more information take a look at the [LICENSE] file.
|
||||
|
||||
|
||||
[User Guide]: https://rust-lang-nursery.github.io/mdBook/
|
||||
[User Guide]: https://rust-lang.github.io/mdBook/
|
||||
[API docs]: https://docs.rs/mdbook/*/mdbook/
|
||||
[E-Easy]: https://github.com/rust-lang-nursery/mdBook/issues?q=is%3Aopen+is%3Aissue+label%3AE-Easy
|
||||
[contribution guide]: https://github.com/rust-lang-nursery/mdBook/blob/master/CONTRIBUTING.md
|
||||
[LICENSE]: https://github.com/rust-lang-nursery/mdBook/blob/master/LICENSE
|
||||
[releases]: https://github.com/rust-lang-nursery/mdBook/releases
|
||||
[E-Easy]: https://github.com/rust-lang/mdBook/issues?q=is%3Aopen+is%3Aissue+label%3AE-Easy
|
||||
[contribution guide]: https://github.com/rust-lang/mdBook/blob/master/CONTRIBUTING.md
|
||||
[LICENSE]: https://github.com/rust-lang/mdBook/blob/master/LICENSE
|
||||
[releases]: https://github.com/rust-lang/mdBook/releases
|
||||
[Rust]: https://www.rust-lang.org/
|
||||
[CLI docs]: http://rust-lang-nursery.github.io/mdBook/cli/init.html
|
||||
[master-docs]: http://rust-lang-nursery.github.io/mdBook/mdbook/
|
||||
[CLI docs]: http://rust-lang.github.io/mdBook/cli/init.html
|
||||
[master-docs]: http://rust-lang.github.io/mdBook/
|
||||
[`linkcheck`]: https://crates.io/crates/mdbook-linkcheck
|
||||
[`epub`]: https://crates.io/crates/mdbook-epub
|
||||
|
||||
69
appveyor.yml
69
appveyor.yml
@@ -1,69 +0,0 @@
|
||||
environment:
|
||||
global:
|
||||
PROJECT_NAME: mdBook
|
||||
nodejs_version: "6"
|
||||
matrix:
|
||||
# Stable channel
|
||||
- TARGET: i686-pc-windows-msvc
|
||||
RUST_CHANNEL: stable
|
||||
- TARGET: x86_64-pc-windows-msvc
|
||||
RUST_CHANNEL: stable
|
||||
# Beta channel
|
||||
- TARGET: i686-pc-windows-msvc
|
||||
RUST_CHANNEL: beta
|
||||
- TARGET: x86_64-pc-windows-msvc
|
||||
RUST_CHANNEL: beta
|
||||
# Nightly channel
|
||||
- TARGET: i686-pc-windows-msvc
|
||||
RUST_CHANNEL: nightly
|
||||
- TARGET: x86_64-pc-windows-msvc
|
||||
RUST_CHANNEL: nightly
|
||||
|
||||
# Install Rust and Cargo
|
||||
install:
|
||||
- ps: >-
|
||||
If ($Env:TARGET -eq 'x86_64-pc-windows-gnu') {
|
||||
$Env:PATH += ';C:\msys64\mingw64\bin'
|
||||
} ElseIf ($Env:TARGET -eq 'i686-pc-windows-gnu') {
|
||||
$Env:PATH += ';C:\msys64\mingw32\bin'
|
||||
}
|
||||
- curl -sSf -o rustup-init.exe https://win.rustup.rs/
|
||||
- rustup-init.exe -y --default-host %TARGET% --default-toolchain %RUST_CHANNEL%
|
||||
- set PATH=%PATH%;C:\Users\appveyor\.cargo\bin
|
||||
- rustc -Vv
|
||||
- cargo -V
|
||||
- ps: Install-Product node $env:nodejs_version
|
||||
- node --version
|
||||
- npm --version
|
||||
- npm install -g stylus nib
|
||||
|
||||
build: false
|
||||
|
||||
# Equivalent to Travis' `script` phase
|
||||
test_script:
|
||||
- cargo build --verbose
|
||||
- cargo build --verbose --features=regenerate-css
|
||||
- cargo test --verbose
|
||||
|
||||
before_deploy:
|
||||
# Generate artifacts for release
|
||||
- cargo build --release
|
||||
- mkdir staging
|
||||
- copy target\release\mdbook.exe staging
|
||||
- cd staging
|
||||
- 7z a ../%PROJECT_NAME%-%APPVEYOR_REPO_TAG_NAME%-%TARGET%.zip *
|
||||
- appveyor PushArtifact ../%PROJECT_NAME%-%APPVEYOR_REPO_TAG_NAME%-%TARGET%.zip
|
||||
|
||||
deploy:
|
||||
description: 'Windows release'
|
||||
artifact: /.*\.zip/
|
||||
auth_token:
|
||||
secure: QQhjKVyz7mpjlyGhlXytbFQQfKFQWTahHkD+B0NzIUoEVqO7ZLWjnoWasvLqW4nE
|
||||
provider: GitHub
|
||||
on:
|
||||
RUST_CHANNEL: stable
|
||||
appveyor_repo_tag: true
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
@@ -1,10 +0,0 @@
|
||||
[book]
|
||||
title = "mdBook Documentation"
|
||||
description = "Create book from markdown files. Like Gitbook but implemented in Rust"
|
||||
authors = ["Mathieu David", "Michael-F-Bryan"]
|
||||
|
||||
[output.html]
|
||||
mathjax-support = true
|
||||
|
||||
[output.html.playpen]
|
||||
editable = true
|
||||
@@ -1,15 +0,0 @@
|
||||
# mdBook
|
||||
|
||||
**mdBook** is a command line tool and Rust crate to create books using Markdown files. It's very similar to Gitbook but written in [Rust](http://www.rust-lang.org).
|
||||
|
||||
What you are reading serves as an example of the output of mdBook and at the same time as a high-level documentation.
|
||||
|
||||
mdBook is free and open source, you can find the source code on [Github](https://github.com/rust-lang-nursery/mdBook). Issues and feature requests can be posted on the [Github Issue tracker](https://github.com/rust-lang-nursery/mdBook/issues).
|
||||
|
||||
## API docs
|
||||
|
||||
Alongside this book you can also read the [API docs](https://docs.rs/mdbook/*/mdbook/) generated by Rustdoc if you would like to use mdBook as a crate or write a new renderer and need a more low-level overview.
|
||||
|
||||
## License
|
||||
|
||||
mdBook, all the source code, is released under the [Mozilla Public License v2.0](https://www.mozilla.org/MPL/2.0/)
|
||||
@@ -1,23 +0,0 @@
|
||||
# Summary
|
||||
|
||||
- [mdBook](README.md)
|
||||
- [Command Line Tool](cli/cli-tool.md)
|
||||
- [init](cli/init.md)
|
||||
- [build](cli/build.md)
|
||||
- [watch](cli/watch.md)
|
||||
- [serve](cli/serve.md)
|
||||
- [test](cli/test.md)
|
||||
- [Format](format/format.md)
|
||||
- [SUMMARY.md](format/summary.md)
|
||||
- [Configuration](format/config.md)
|
||||
- [Theme](format/theme/theme.md)
|
||||
- [index.hbs](format/theme/index-hbs.md)
|
||||
- [Syntax highlighting](format/theme/syntax-highlighting.md)
|
||||
- [Editor](format/theme/editor.md)
|
||||
- [MathJax Support](format/mathjax.md)
|
||||
- [mdBook specific features](format/mdbook.md)
|
||||
- [For Developers](for_developers/index.md)
|
||||
- [Preprocessors](for_developers/preprocessors.md)
|
||||
- [Alternate Backends](for_developers/backends.md)
|
||||
-----------
|
||||
[Contributors](misc/contributors.md)
|
||||
@@ -1,35 +0,0 @@
|
||||
# The build command
|
||||
|
||||
The build command is used to render your book:
|
||||
|
||||
```bash
|
||||
mdbook build
|
||||
```
|
||||
|
||||
It will try to parse your `SUMMARY.md` file to understand the structure of your book
|
||||
and fetch the corresponding files.
|
||||
|
||||
The rendered output will maintain the same directory structure as the source for
|
||||
convenience. Large books will therefore remain structured when rendered.
|
||||
|
||||
#### Specify a directory
|
||||
|
||||
Like `init`, the `build` command can take a directory as argument to use instead of the
|
||||
current working directory.
|
||||
|
||||
```bash
|
||||
mdbook build path/to/book
|
||||
```
|
||||
|
||||
#### --open
|
||||
|
||||
When you use the `--open` (`-o`) option, mdbook will open the rendered book in
|
||||
your default web browser after building it.
|
||||
|
||||
#### --dest-dir
|
||||
|
||||
The `--dest-dir` (`-d`) option allows you to change the output directory for your book.
|
||||
|
||||
-------------------
|
||||
|
||||
***note:*** *make sure to run the build command in the root directory and not in the source directory*
|
||||
@@ -1,35 +0,0 @@
|
||||
# Command Line Tool
|
||||
|
||||
mdBook can be used either as a command line tool or a [Rust crate](https://crates.io/crates/mdbook).
|
||||
Let's focus on the command line tool capabilities first.
|
||||
|
||||
## Install
|
||||
|
||||
### Pre-requisite
|
||||
|
||||
mdBook is written in **[Rust](https://www.rust-lang.org/)** and therefore needs to be compiled with **Cargo**, because we don't yet offer ready-to-go binaries. If you haven't already installed Rust, please go ahead and [install it](https://www.rust-lang.org/downloads.html) now.
|
||||
|
||||
### Install Crates.io version
|
||||
|
||||
Installing mdBook is relatively easy if you already have Rust and Cargo installed. You just have to type this snippet in your terminal:
|
||||
|
||||
```bash
|
||||
cargo install mdbook
|
||||
```
|
||||
|
||||
This will fetch the source code from [Crates.io](https://crates.io/) and compile it. You will have to add Cargo's `bin` directory to your `PATH`.
|
||||
|
||||
Run `mdbook help` in your terminal to verify if it works. Congratulations, you have installed mdBook!
|
||||
|
||||
|
||||
### Install Git version
|
||||
|
||||
The **[git version](https://github.com/rust-lang-nursery/mdBook)** contains all the latest bug-fixes and features, that will be released in the next version on **Crates.io**, if you can't wait until the next release. You can build the git version yourself. Open your terminal and navigate to the directory of you choice. We need to clone the git repository and then build it with Cargo.
|
||||
|
||||
```bash
|
||||
git clone --depth=1 https://github.com/rust-lang-nursery/mdBook.git
|
||||
cd mdBook
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
The executable `mdbook` will be in the `./target/release` folder, this should be added to the path.
|
||||
@@ -1,45 +0,0 @@
|
||||
# The init command
|
||||
There is some minimal boilerplate that is the same for every new book. It's for this purpose that mdBook includes an `init` command.
|
||||
|
||||
The `init` command is used like this:
|
||||
|
||||
```bash
|
||||
mdbook init
|
||||
```
|
||||
|
||||
When using the `init` command for the first time, a couple of files will be set up for you:
|
||||
```bash
|
||||
book-test/
|
||||
├── book
|
||||
└── src
|
||||
├── chapter_1.md
|
||||
└── SUMMARY.md
|
||||
```
|
||||
|
||||
- The `src` directory is were you write your book in markdown. It contains all the source files,
|
||||
configuration files, etc.
|
||||
|
||||
- The `book` directory is where your book is rendered. All the output is ready to be uploaded
|
||||
to a server to be seen by your audience.
|
||||
|
||||
- The `SUMMARY.md` file is the most important file, it's the skeleton of your book and is discussed in more detail in another [chapter](format/summary.html).
|
||||
|
||||
#### Tip & Trick: Hidden Feature
|
||||
When a `SUMMARY.md` file already exists, the `init` command will first parse it and generate the missing files according to the paths used in the `SUMMARY.md`. This allows you to think and create the whole structure of your book and then let mdBook generate it for you.
|
||||
|
||||
#### Specify a directory
|
||||
|
||||
When using the `init` command, you can also specify a directory, instead of using the current working directory,
|
||||
by appending a path to the command:
|
||||
|
||||
```bash
|
||||
mdbook init path/to/book
|
||||
```
|
||||
|
||||
## --theme
|
||||
|
||||
When you use the `--theme` argument, the default theme will be copied into a directory
|
||||
called `theme` in your source directory so that you can modify it.
|
||||
|
||||
The theme is selectively overwritten, this means that if you don't want to overwrite a
|
||||
specific file, just delete it and the default file will be used.
|
||||
@@ -1,40 +0,0 @@
|
||||
# The serve command
|
||||
|
||||
The `serve` command is useful when you want to preview your book. It also does hot reloading of the webpage whenever a file changes.
|
||||
It achieves this by serving the books content over `localhost:3000` (unless otherwise configured, see below) and runs a websocket server on `localhost:3001` which triggers the reloads.
|
||||
This preferred by many for writing books with mdbook because it allows for you to see the result of your work instantly after every file change.
|
||||
|
||||
#### Specify a directory
|
||||
|
||||
Like `watch`, `serve` can take a directory as argument to use instead of the
|
||||
current working directory.
|
||||
|
||||
```bash
|
||||
mdbook serve path/to/book
|
||||
```
|
||||
|
||||
|
||||
#### Server options
|
||||
|
||||
`serve` has four options: the http port, the websocket port, the interface to serve on, and the public address of the server so that the browser may reach the websocket server.
|
||||
|
||||
For example: suppose you had an nginx server for SSL termination which has a public address of 192.168.1.100 on port 80 and proxied that to 127.0.0.1 on port 8000. To run use the nginx proxy do:
|
||||
|
||||
```bash
|
||||
mdbook serve path/to/book -p 8000 -i 127.0.0.1 -a 192.168.1.100
|
||||
```
|
||||
|
||||
If you were to want live reloading for this you would need to proxy the websocket calls through nginx as well from `192.168.1.100:<WS_PORT>` to `127.0.0.1:<WS_PORT>`. The `-w` flag allows for the websocket port to be configured.
|
||||
|
||||
#### --open
|
||||
|
||||
When you use the `--open` (`-o`) option, mdbook will open the book in your
|
||||
your default web browser after starting the server.
|
||||
|
||||
#### --dest-dir
|
||||
|
||||
The `--dest-dir` (`-d`) option allows you to change the output directory for your book.
|
||||
|
||||
-----
|
||||
|
||||
***note:*** *the `serve` command has not gotten a lot of testing yet, there could be some rough edges. If you discover a problem, please report it [on Github](https://github.com/rust-lang-nursery/mdBook/issues)*
|
||||
@@ -1,19 +0,0 @@
|
||||
# The test command
|
||||
|
||||
When writing a book, you sometimes need to automate some tests. For example, [The Rust Programming Book](https://doc.rust-lang.org/stable/book/) uses a lot of code examples that could get outdated.
|
||||
Therefore it is very important for them to be able to automatically test these code examples.
|
||||
|
||||
mdBook supports a `test` command that will run all available tests in mdBook. At the moment, only one test is available:
|
||||
*"Test Rust code examples using Rustdoc"*, but I hope this will be expanded in the future to include more tests like:
|
||||
|
||||
- checking for broken links
|
||||
- checking for unused files
|
||||
- ...
|
||||
|
||||
In the future I would like the user to be able to enable / disable test from the `book.toml` configuration file and support custom tests.
|
||||
|
||||
**How to use it:**
|
||||
```bash
|
||||
$ mdbook test
|
||||
[*]: Testing file: "/mdBook/book-example/src/README.md”
|
||||
```
|
||||
@@ -1,26 +0,0 @@
|
||||
# The watch command
|
||||
|
||||
The `watch` command is useful when you want your book to be rendered on every file change.
|
||||
You could repeatedly issue `mdbook build` every time a file is changed. But using `mdbook watch` once will watch your files and will trigger a build automatically whenever you modify a file.
|
||||
|
||||
#### Specify a directory
|
||||
|
||||
Like `init` and `build`, `watch` can take a directory as argument to use instead of the
|
||||
current working directory.
|
||||
|
||||
```bash
|
||||
mdbook watch path/to/book
|
||||
```
|
||||
|
||||
#### --open
|
||||
|
||||
When you use the `--open` (`-o`) option, mdbook will open the rendered book in
|
||||
your default web browser.
|
||||
|
||||
#### --dest-dir
|
||||
|
||||
The `--dest-dir` (`-d`) option allows you to change the output directory for your book.
|
||||
|
||||
-----
|
||||
|
||||
***note:*** *the `watch` command has not gotten a lot of testing yet, there could be some rough edges. If you discover a problem, please report it [on Github](https://github.com/rust-lang-nursery/mdBook/issues)*
|
||||
@@ -1,32 +0,0 @@
|
||||
# Preprocessors
|
||||
|
||||
A *preprocessor* is simply a bit of code which gets run immediately after the
|
||||
book is loaded and before it gets rendered, allowing you to update and mutate
|
||||
the book. Possible use cases are:
|
||||
|
||||
- Creating custom helpers like `\{{#include /path/to/file.md}}`
|
||||
- Updating links so `[some chapter](some_chapter.md)` is automatically changed
|
||||
to `[some chapter](some_chapter.html)` for the HTML renderer
|
||||
- Substituting in latex-style expressions (`$$ \frac{1}{3} $$`) with their
|
||||
mathjax equivalents
|
||||
|
||||
|
||||
## Implementing a Preprocessor
|
||||
|
||||
A preprocessor is represented by the `Preprocessor` trait.
|
||||
|
||||
```rust
|
||||
pub trait Preprocessor {
|
||||
fn name(&self) -> &str;
|
||||
fn run(&self, ctx: &PreprocessorContext, book: &mut Book) -> Result<()>;
|
||||
}
|
||||
```
|
||||
|
||||
Where the `PreprocessorContext` is defined as
|
||||
|
||||
```rust
|
||||
pub struct PreprocessorContext {
|
||||
pub root: PathBuf,
|
||||
pub config: Config,
|
||||
}
|
||||
```
|
||||
@@ -1,148 +0,0 @@
|
||||
# Configuration
|
||||
|
||||
You can configure the parameters for your book in the ***book.toml*** file.
|
||||
|
||||
Here is an example of what a ***book.toml*** file might look like:
|
||||
|
||||
```toml
|
||||
[book]
|
||||
title = "Example book"
|
||||
author = "John Doe"
|
||||
description = "The example book covers examples."
|
||||
|
||||
[build]
|
||||
build-dir = "my-example-book"
|
||||
create-missing = false
|
||||
|
||||
[output.html]
|
||||
additional-css = ["custom.css"]
|
||||
```
|
||||
|
||||
## Supported configuration options
|
||||
|
||||
It is important to note that **any** relative path specified in the in the configuration will
|
||||
always be taken relative from the root of the book where the configuration file is located.
|
||||
|
||||
|
||||
### General metadata
|
||||
|
||||
This is general information about your book.
|
||||
|
||||
- **title:** The title of the book
|
||||
- **authors:** The author(s) of the book
|
||||
- **description:** A description for the book, which is added as meta
|
||||
information in the html `<head>` of each page
|
||||
- **src:** By default, the source directory is found in the directory named
|
||||
`src` directly under the root folder. But this is configurable with the `src`
|
||||
key in the configuration file.
|
||||
|
||||
**book.toml**
|
||||
```toml
|
||||
[book]
|
||||
title = "Example book"
|
||||
authors = ["John Doe", "Jane Doe"]
|
||||
description = "The example book covers examples."
|
||||
src = "my-src" # the source files will be found in `root/my-src` instead of `root/src`
|
||||
```
|
||||
|
||||
### Build options
|
||||
|
||||
This controls the build process of your book.
|
||||
|
||||
- **build-dir:** The directory to put the rendered book in. By default this is
|
||||
`book/` in the book's root directory.
|
||||
- **create-missing:** By default, any missing files specified in `SUMMARY.md`
|
||||
will be created when the book is built (i.e. `create-missing = true`). If this
|
||||
is `false` then the build process will instead exit with an error if any files
|
||||
do not exist.
|
||||
|
||||
**book.toml**
|
||||
```toml
|
||||
[build]
|
||||
build-dir = "build"
|
||||
create-missing = false
|
||||
```
|
||||
|
||||
### HTML renderer options
|
||||
The HTML renderer has a couple of options as well. All the options for the
|
||||
renderer need to be specified under the TOML table `[output.html]`.
|
||||
|
||||
The following configuration options are available:
|
||||
|
||||
- **theme:** mdBook comes with a default theme and all the resource files
|
||||
needed for it. But if this option is set, mdBook will selectively overwrite
|
||||
the theme files with the ones found in the specified folder.
|
||||
- **curly-quotes:** Convert straight quotes to curly quotes, except for
|
||||
those that occur in code blocks and code spans. Defaults to `false`.
|
||||
- **google-analytics:** If you use Google Analytics, this option lets you
|
||||
enable it by simply specifying your ID in the configuration file.
|
||||
- **additional-css:** If you need to slightly change the appearance of your
|
||||
book without overwriting the whole style, you can specify a set of
|
||||
stylesheets that will be loaded after the default ones where you can
|
||||
surgically change the style.
|
||||
- **additional-js:** If you need to add some behaviour to your book without
|
||||
removing the current behaviour, you can specify a set of javascript files
|
||||
that will be loaded alongside the default one.
|
||||
- **playpen:** A subtable for configuring various playpen settings.
|
||||
- **no-section-label**: mdBook by defaults adds section label in table of
|
||||
contents column. For example, "1.", "2.1". Set this option to true to
|
||||
disable those labels. Defaults to `false`.
|
||||
|
||||
**book.toml**
|
||||
```toml
|
||||
[book]
|
||||
title = "Example book"
|
||||
authors = ["John Doe", "Jane Doe"]
|
||||
description = "The example book covers examples."
|
||||
|
||||
[output.html]
|
||||
theme = "my-theme"
|
||||
curly-quotes = true
|
||||
google-analytics = "123456"
|
||||
additional-css = ["custom.css", "custom2.css"]
|
||||
additional-js = ["custom.js"]
|
||||
|
||||
[output.html.playpen]
|
||||
editor = "./path/to/editor"
|
||||
editable = false
|
||||
```
|
||||
|
||||
|
||||
## Environment Variables
|
||||
|
||||
All configuration values van be overridden from the command line by setting the
|
||||
corresponding environment variable. Because many operating systems restrict
|
||||
environment variables to be alphanumeric characters or `_`, the configuration
|
||||
key needs to be formatted slightly differently to the normal `foo.bar.baz` form.
|
||||
|
||||
Variables starting with `MDBOOK_` are used for configuration. The key is
|
||||
created by removing the `MDBOOK_` prefix and turning the resulting
|
||||
string into `kebab-case`. Double underscores (`__`) separate nested
|
||||
keys, while a single underscore (`_`) is replaced with a dash (`-`).
|
||||
|
||||
For example:
|
||||
|
||||
- `MDBOOK_foo` -> `foo`
|
||||
- `MDBOOK_FOO` -> `foo`
|
||||
- `MDBOOK_FOO__BAR` -> `foo.bar`
|
||||
- `MDBOOK_FOO_BAR` -> `foo-bar`
|
||||
- `MDBOOK_FOO_bar__baz` -> `foo-bar.baz`
|
||||
|
||||
So by setting the `MDBOOK_BOOK__TITLE` environment variable you can
|
||||
override the book's title without needing to touch your `book.toml`.
|
||||
|
||||
> **Note:** To facilitate setting more complex config items, the value
|
||||
> of an environment variable is first parsed as JSON, falling back to a
|
||||
> string if the parse fails.
|
||||
>
|
||||
> This means, if you so desired, you could override all book metadata
|
||||
> when building the book with something like
|
||||
>
|
||||
> ```text
|
||||
> $ export MDBOOK_BOOK="{'title': 'My Awesome Book', authors: ['Michael-F-Bryan']}"
|
||||
> $ mdbook build
|
||||
> ```
|
||||
|
||||
The latter case may be useful in situations where `mdbook` is invoked
|
||||
from a script or CI, where it sometimes isn't possible to update the
|
||||
`book.toml` before building.
|
||||
@@ -1,6 +0,0 @@
|
||||
fn main() {
|
||||
println!("Hello World!");
|
||||
#
|
||||
# // You can even hide lines! :D
|
||||
# println!("I am hidden! Expand the code snippet to see me");
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
# MathJax Support
|
||||
|
||||
mdBook has optional support for math equations through [MathJax](https://www.mathjax.org/).
|
||||
|
||||
To enable MathJax, you need to add the `mathjax-support` key to your `book.toml` under the `output.html` section.
|
||||
|
||||
```toml
|
||||
[output.html]
|
||||
mathjax-support = true
|
||||
```
|
||||
|
||||
>**Note:**
|
||||
The usual delimiters MathJax uses are not yet supported. You can't currently use `$$ ... $$` as delimiters and the `\[ ... \]` delimiters need an extra backslash to work. Hopefully this limitation will be lifted soon.
|
||||
|
||||
### Inline equations
|
||||
Inline equations are delimited by `\\(` and `\\)`. So for example, to render the following inline equation \\( \int x dx = \frac{x^2}{2} + C \\) you would write the following:
|
||||
```
|
||||
\\( \int x dx = \frac{x^2}{2} + C \\)
|
||||
```
|
||||
|
||||
### Block equations
|
||||
Block equations are delimited by `\\[` and `\\]`. To render the following equation
|
||||
|
||||
\\[ \mu = \frac{1}{N} \sum_{i=0} x_i \\]
|
||||
|
||||
|
||||
you would write:
|
||||
|
||||
```bash
|
||||
\\[ \mu = \frac{1}{N} \sum_{i=0} x_i \\]
|
||||
```
|
||||
@@ -1,64 +0,0 @@
|
||||
# mdBook specific markdown
|
||||
|
||||
## Hiding code lines
|
||||
|
||||
There is a feature in mdBook that let's you hide code lines by prepending them with a `#`.
|
||||
|
||||
```bash
|
||||
# fn main() {
|
||||
let x = 5;
|
||||
let y = 6;
|
||||
|
||||
println!("{}", x + y);
|
||||
# }
|
||||
```
|
||||
|
||||
Will render as
|
||||
|
||||
```rust
|
||||
# fn main() {
|
||||
let x = 5;
|
||||
let y = 7;
|
||||
|
||||
println!("{}", x + y);
|
||||
# }
|
||||
```
|
||||
|
||||
## Including files
|
||||
|
||||
With the following syntax, you can include files into your book:
|
||||
|
||||
```hbs
|
||||
\{{#include file.rs}}
|
||||
```
|
||||
|
||||
The path to the file has to be relative from the current source file.
|
||||
|
||||
Usually, this command is used for including code snippets and examples. In this case, oftens one would include a specific part of the file e.g. which only contains the relevant lines for the example. We support four different modes of partial includes:
|
||||
|
||||
```hbs
|
||||
\{{#include file.rs:2}}
|
||||
\{{#include file.rs::10}}
|
||||
\{{#include file.rs:2:}}
|
||||
\{{#include file.rs:2:10}}
|
||||
```
|
||||
|
||||
The first command only includes the second line from file `file.rs`. The second command includes all lines up to line 10, i.e. the lines from 11 till the end of the file are omitted. The third command includes all lines from line 2, i.e. the first line is omitted. The last command includes the excerpt of `file.rs` consisting of lines 2 to 10.
|
||||
|
||||
## Inserting runnable Rust files
|
||||
|
||||
With the following syntax, you can insert runnable Rust files into your book:
|
||||
|
||||
```hbs
|
||||
\{{#playpen file.rs}}
|
||||
```
|
||||
|
||||
The path to the Rust file has to be relative from the current source file.
|
||||
|
||||
When play is clicked, the code snippet will be send to the [Rust Playpen] to be compiled and run. The result is send back and displayed directly underneath the code.
|
||||
|
||||
Here is what a rendered code snippet looks like:
|
||||
|
||||
{{#playpen example.rs}}
|
||||
|
||||
[Rust Playpen]: https://play.rust-lang.org/
|
||||
@@ -1,32 +0,0 @@
|
||||
# SUMMARY.md
|
||||
|
||||
The summary file is used by mdBook to know what chapters to include,
|
||||
in what order they should appear, what their hierarchy is and where the source files are.
|
||||
Without this file, there is no book.
|
||||
|
||||
Even though `SUMMARY.md` is a markdown file, the formatting is very strict to
|
||||
allow for easy parsing. Let's see how you should format your `SUMMARY.md` file.
|
||||
|
||||
#### Allowed elements
|
||||
|
||||
1. ***Title*** It's common practice to begin with a title, generally
|
||||
<code class="language-markdown"># Summary</code>.
|
||||
But it is not mandatory, the parser just ignores it. So you can too
|
||||
if you feel like it.
|
||||
|
||||
2. ***Prefix Chapter*** Before the main numbered chapters you can add a couple of elements that will not be numbered. This is useful for
|
||||
forewords, introductions, etc. There are however some constraints. You can not nest prefix chapters, they should all be on the root level. And you can not add prefix chapters once you have added numbered chapters.
|
||||
```markdown
|
||||
[Title of prefix element](relative/path/to/markdown.md)
|
||||
```
|
||||
|
||||
3. ***Numbered Chapter*** Numbered chapters are the main content of the book, they will be numbered and can be nested,
|
||||
resulting in a nice hierarchy (chapters, sub-chapters, etc.)
|
||||
```markdown
|
||||
- [Title of the Chapter](relative/path/to/markdown.md)
|
||||
```
|
||||
You can either use `-` or `*` to indicate a numbered chapter.
|
||||
|
||||
4. ***Suffix Chapter*** After the numbered chapters you can add a couple of non-numbered chapters. They are the same as prefix chapters but come after the numbered chapters instead of before.
|
||||
|
||||
All other elements are unsupported and will be ignored at best or result in an error.
|
||||
@@ -1,40 +0,0 @@
|
||||
# Editor
|
||||
|
||||
In addition to providing runnable code playpens, mdBook optionally allows them to be editable. In order to enable editable code blocks, the following needs to be added to the ***book.toml***:
|
||||
|
||||
```toml
|
||||
[output.html.playpen]
|
||||
editable = true
|
||||
```
|
||||
|
||||
To make a specific block available for editing, the attribute `editable` needs to be added to it:
|
||||
|
||||
<pre><code class="language-markdown">```rust,editable
|
||||
fn main() {
|
||||
let number = 5;
|
||||
print!("{}", number);
|
||||
}
|
||||
```</code></pre>
|
||||
|
||||
The above will result in this editable playpen:
|
||||
|
||||
```rust,editable
|
||||
fn main() {
|
||||
let number = 5;
|
||||
print!("{}", number);
|
||||
}
|
||||
```
|
||||
|
||||
Note the new `Undo Changes` button in the editable playpens.
|
||||
|
||||
## Customizing the Editor
|
||||
|
||||
By default, the editor is the [Ace](https://ace.c9.io/) editor, but, if desired, the functionality may be overriden by providing a different folder:
|
||||
|
||||
```toml
|
||||
[output.html.playpen]
|
||||
editable = true
|
||||
editor = "/path/to/editor"
|
||||
```
|
||||
|
||||
Note that for the editor changes to function correctly, the `book.js` inside of the `theme` folder will need to be overriden as it has some couplings with the default Ace editor.
|
||||
@@ -1,91 +0,0 @@
|
||||
# index.hbs
|
||||
|
||||
`index.hbs` is the handlebars template that is used to render the book.
|
||||
The markdown files are processed to html and then injected in that template.
|
||||
|
||||
If you want to change the layout or style of your book, chances are that you will
|
||||
have to modify this template a little bit. Here is what you need to know.
|
||||
|
||||
## Data
|
||||
|
||||
A lot of data is exposed to the handlebars template with the "context".
|
||||
In the handlebars template you can access this information by using
|
||||
|
||||
```handlebars
|
||||
{{name_of_property}}
|
||||
```
|
||||
|
||||
Here is a list of the properties that are exposed:
|
||||
|
||||
- ***language*** Language of the book in the form `en`. To use in <code class="language-html">\<html lang="{{ language }}"></code> for example.
|
||||
At the moment it is hardcoded.
|
||||
- ***title*** Title of the book, as specified in `book.toml`
|
||||
- ***chapter_title*** Title of the current chapter, as listed in `SUMMARY.md`
|
||||
|
||||
- ***path*** Relative path to the original markdown file from the source directory
|
||||
- ***content*** This is the rendered markdown.
|
||||
- ***path_to_root*** This is a path containing exclusively `../`'s that points to the root of the book from the current file.
|
||||
Since the original directory structure is maintained, it is useful to prepend relative links with this `path_to_root`.
|
||||
|
||||
- ***chapters*** Is an array of dictionaries of the form
|
||||
```json
|
||||
{"section": "1.2.1", "name": "name of this chapter", "path": "dir/markdown.md"}
|
||||
```
|
||||
containing all the chapters of the book. It is used for example to construct the table of contents (sidebar).
|
||||
|
||||
## Handlebars Helpers
|
||||
|
||||
In addition to the properties you can access, there are some handlebars helpers at your disposal.
|
||||
|
||||
1. ### toc
|
||||
|
||||
The toc helper is used like this
|
||||
|
||||
```handlebars
|
||||
{{#toc}}{{/toc}}
|
||||
```
|
||||
|
||||
and outputs something that looks like this, depending on the structure of your book
|
||||
|
||||
```html
|
||||
<ul class="chapter">
|
||||
<li><a href="link/to/file.html">Some chapter</a></li>
|
||||
<li>
|
||||
<ul class="section">
|
||||
<li><a href="link/to/other_file.html">Some other Chapter</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
```
|
||||
|
||||
If you would like to make a toc with another structure, you have access to the chapters property containing all the data.
|
||||
The only limitation at the moment is that you would have to do it with JavaScript instead of with a handlebars helper.
|
||||
|
||||
```html
|
||||
<script>
|
||||
var chapters = {{chapters}};
|
||||
// Processing here
|
||||
</script>
|
||||
```
|
||||
|
||||
2. ### previous / next
|
||||
|
||||
The previous and next helpers expose a `link` and `name` property to the previous and next chapters.
|
||||
|
||||
They are used like this
|
||||
|
||||
```handlebars
|
||||
{{#previous}}
|
||||
<a href="{{link}}" class="nav-chapters previous">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
{{/previous}}
|
||||
```
|
||||
|
||||
The inner html will only be rendered if the previous / next chapter exists.
|
||||
Of course the inner html can be changed to your liking.
|
||||
|
||||
------
|
||||
|
||||
*If you would like me to expose other properties or helpers, please [create a new issue](https://github.com/rust-lang-nursery/mdBook/issues)
|
||||
and I will consider it.*
|
||||
@@ -1,60 +0,0 @@
|
||||
# Syntax Highlighting
|
||||
|
||||
For syntax highlighting I use [Highlight.js](https://highlightjs.org) with a custom theme.
|
||||
|
||||
Automatic language detection has been turned off, so you will probably want to
|
||||
specify the programming language you use like this
|
||||
|
||||
<pre><code class="language-markdown">```rust
|
||||
fn main() {
|
||||
// Some code
|
||||
}
|
||||
```</code></pre>
|
||||
|
||||
## Custom theme
|
||||
Like the rest of the theme, the files used for syntax highlighting can be overridden with your own.
|
||||
|
||||
- ***highlight.js*** normally you shouldn't have to overwrite this file, unless you want to use a more recent version.
|
||||
- ***highlight.css*** theme used by highlight.js for syntax highlighting.
|
||||
|
||||
If you want to use another theme for `highlight.js` download it from their website, or make it yourself,
|
||||
rename it to `highlight.css` and put it in `src/theme` (or the equivalent if you changed your source folder)
|
||||
|
||||
Now your theme will be used instead of the default theme.
|
||||
|
||||
## Hiding code lines
|
||||
|
||||
There is a feature in mdBook that let's you hide code lines by prepending them with a `#`.
|
||||
|
||||
|
||||
```bash
|
||||
# fn main() {
|
||||
let x = 5;
|
||||
let y = 6;
|
||||
|
||||
println!("{}", x + y);
|
||||
# }
|
||||
```
|
||||
|
||||
Will render as
|
||||
|
||||
```rust
|
||||
# fn main() {
|
||||
let x = 5;
|
||||
let y = 7;
|
||||
|
||||
println!("{}", x + y);
|
||||
# }
|
||||
```
|
||||
|
||||
**At the moment, this only works for code examples that are annotated with `rust`. Because it would collide with semantics of some programming languages. In the future, we want to make this configurable through the `book.toml` so that everyone can benefit from it.**
|
||||
|
||||
|
||||
## Improve default theme
|
||||
|
||||
If you think the default theme doesn't look quite right for a specific language, or could be improved.
|
||||
Feel free to [submit a new issue](https://github.com/rust-lang-nursery/mdBook/issues) explaining what you have in mind and I will take a look at it.
|
||||
|
||||
You could also create a pull-request with the proposed improvements.
|
||||
|
||||
Overall the theme should be light and sober, without to many flashy colors.
|
||||
@@ -1,22 +0,0 @@
|
||||
# Theme
|
||||
|
||||
The default renderer uses a [handlebars](http://handlebarsjs.com/) template to render your markdown files and comes with a default theme
|
||||
included in the mdBook binary.
|
||||
|
||||
The theme is totally customizable, you can selectively replace every file from the theme by your own by adding a
|
||||
`theme` directory next to `src` folder in your project root. Create a new file with the name of the file you want to override
|
||||
and now that file will be used instead of the default file.
|
||||
|
||||
Here are the files you can override:
|
||||
|
||||
- ***index.hbs*** is the handlebars template.
|
||||
- ***book.css*** is the style used in the output. If you want to change the design of your book, this is probably the file you want to modify. Sometimes in conjunction with `index.hbs` when you want to radically change the layout.
|
||||
- ***book.js*** is mostly used to add client side functionality, like hiding / un-hiding the sidebar, changing the theme, ...
|
||||
- ***highlight.js*** is the JavaScript that is used to highlight code snippets, you should not need to modify this.
|
||||
- ***highlight.css*** is the theme used for the code highlighting
|
||||
- ***favicon.png*** the favicon that will be used
|
||||
|
||||
Generally, when you want to tweak the theme, you don't need to override all the files. If you only need changes in the stylesheet,
|
||||
there is no point in overriding all the other files. Because custom files take precedence over built-in ones, they will not get updated with new fixes / features.
|
||||
|
||||
**Note:** When you override a file, it is possible that you break some functionality. Therefore I recommend to use the file from the default theme as template and only add / modify what you need. You can copy the default theme into your source directory automatically by using `mdbook init --theme` just remove the files you don't want to override.
|
||||
98
build.rs
98
build.rs
@@ -1,98 +0,0 @@
|
||||
// build.rs
|
||||
|
||||
use std::env;
|
||||
use std::path::Path;
|
||||
#[macro_use]
|
||||
extern crate error_chain;
|
||||
|
||||
#[cfg(windows)]
|
||||
mod execs {
|
||||
use std::process::Command;
|
||||
|
||||
pub fn cmd(program: &str) -> Command {
|
||||
let mut cmd = Command::new("cmd");
|
||||
cmd.args(&["/c", program]);
|
||||
cmd
|
||||
}
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
mod execs {
|
||||
use std::process::Command;
|
||||
|
||||
pub fn cmd(program: &str) -> Command {
|
||||
Command::new(program)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
error_chain!{
|
||||
foreign_links {
|
||||
Io(std::io::Error);
|
||||
}
|
||||
}
|
||||
|
||||
fn program_exists(program: &str) -> Result<()> {
|
||||
execs::cmd(program).arg("-v")
|
||||
.output()
|
||||
.chain_err(|| format!("Please install '{}'!", program))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn npm_package_exists(package: &str) -> Result<()> {
|
||||
let status = execs::cmd("npm").args(&["list", "-g"])
|
||||
.arg(package)
|
||||
.output();
|
||||
|
||||
match status {
|
||||
Ok(ref out) if out.status.success() => Ok(()),
|
||||
_ => {
|
||||
bail!("Missing npm package '{0}' install with: 'npm -g install {0}'",
|
||||
package)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Resource<'a> {
|
||||
Program(&'a str),
|
||||
Package(&'a str),
|
||||
}
|
||||
use Resource::{Package, Program};
|
||||
|
||||
impl<'a> Resource<'a> {
|
||||
pub fn exists(&self) -> Result<()> {
|
||||
match *self {
|
||||
Program(name) => program_exists(name),
|
||||
Package(name) => npm_package_exists(name),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn run() -> Result<()> {
|
||||
if let Ok(_) = env::var("CARGO_FEATURE_REGENERATE_CSS") {
|
||||
// Check dependencies
|
||||
Program("npm").exists()?;
|
||||
Program("node").exists().or(Program("nodejs").exists())?;
|
||||
Package("nib").exists()?;
|
||||
Package("stylus").exists()?;
|
||||
|
||||
// Compile stylus stylesheet to css
|
||||
let manifest_dir = env::var("CARGO_MANIFEST_DIR")
|
||||
.chain_err(|| "Please run the script with: 'cargo build'!")?;
|
||||
let theme_dir = Path::new(&manifest_dir).join("src/theme/");
|
||||
let stylus_dir = theme_dir.join("stylus/book.styl");
|
||||
|
||||
if !execs::cmd("stylus").arg(stylus_dir)
|
||||
.arg("--out")
|
||||
.arg(theme_dir)
|
||||
.arg("--use")
|
||||
.arg("nib")
|
||||
.status()?
|
||||
.success()
|
||||
{
|
||||
bail!("Stylus encountered an error");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
quick_main!(run);
|
||||
@@ -1,31 +0,0 @@
|
||||
# This script takes care of building your crate and packaging it for release
|
||||
|
||||
set -ex
|
||||
|
||||
main() {
|
||||
local src=$(pwd) \
|
||||
stage=
|
||||
|
||||
case $TRAVIS_OS_NAME in
|
||||
linux)
|
||||
stage=$(mktemp -d)
|
||||
;;
|
||||
osx)
|
||||
stage=$(mktemp -d -t tmp)
|
||||
;;
|
||||
esac
|
||||
|
||||
test -f Cargo.lock || cargo generate-lockfile
|
||||
|
||||
cross rustc --bin mdbook --target $TARGET --release -- -C lto
|
||||
|
||||
cp target/$TARGET/release/mdbook $stage/
|
||||
|
||||
cd $stage
|
||||
tar czf $src/$CRATE_NAME-$TRAVIS_TAG-$TARGET.tar.gz *
|
||||
cd $src
|
||||
|
||||
rm -rf $stage
|
||||
}
|
||||
|
||||
main
|
||||
@@ -1,50 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Deploys the `book-example` to GitHub Pages
|
||||
|
||||
set -ex
|
||||
|
||||
# Only run this on the master branch for stable
|
||||
if [ "$TRAVIS_PULL_REQUEST" != "false" ] ||
|
||||
[ "$TRAVIS_BRANCH" != "master" ] ||
|
||||
[ "$TRAVIS_RUST_VERSION" != "stable" ] ||
|
||||
[ "$TARGET" != "x86_64-unknown-linux-gnu" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Make sure we have the css dependencies
|
||||
npm install -g stylus nib
|
||||
|
||||
NC='\033[39m'
|
||||
CYAN='\033[36m'
|
||||
GREEN='\033[32m'
|
||||
|
||||
rev=$(git rev-parse --short HEAD)
|
||||
|
||||
echo -e "${CYAN}Running cargo doc${NC}"
|
||||
cargo doc --features regenerate-css > /dev/null
|
||||
|
||||
echo -e "${CYAN}Running mdbook build${NC}"
|
||||
cargo run -- build book-example/
|
||||
|
||||
echo -e "${CYAN}Copying book to target/doc${NC}"
|
||||
cp -R book-example/book/* target/doc/
|
||||
|
||||
cd target/doc
|
||||
|
||||
echo -e "${CYAN}Initializing Git${NC}"
|
||||
git init
|
||||
git config user.name "Michael Bryan"
|
||||
git config user.email "michaelfbryan@gmail.com"
|
||||
|
||||
git remote add upstream "https://$GH_TOKEN@github.com/rust-lang-nursery/mdBook.git"
|
||||
git fetch upstream --quiet
|
||||
git reset upstream/gh-pages --quiet
|
||||
|
||||
touch .
|
||||
|
||||
echo -e "${CYAN}Pushing changes to gh-pages${NC}"
|
||||
git add -A .
|
||||
git commit -m "rebuild pages at ${rev}" --quiet
|
||||
git push -q upstream HEAD:gh-pages --quiet
|
||||
|
||||
echo -e "${GREEN}Deployed docs to GitHub Pages${NC}"
|
||||
24
ci/install-hub.sh
Executable file
24
ci/install-hub.sh
Executable file
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env bash
|
||||
# Installs the `hub` executable into hub/bin
|
||||
set -ex
|
||||
case $1 in
|
||||
ubuntu*)
|
||||
curl -LsSf https://github.com/github/hub/releases/download/v2.12.8/hub-linux-amd64-2.12.8.tgz -o hub.tgz
|
||||
mkdir hub
|
||||
tar -xzvf hub.tgz --strip=1 -C hub
|
||||
;;
|
||||
macos*)
|
||||
curl -LsSf https://github.com/github/hub/releases/download/v2.12.8/hub-darwin-amd64-2.12.8.tgz -o hub.tgz
|
||||
mkdir hub
|
||||
tar -xzvf hub.tgz --strip=1 -C hub
|
||||
;;
|
||||
windows*)
|
||||
curl -LsSf https://github.com/github/hub/releases/download/v2.12.8/hub-windows-amd64-2.12.8.zip -o hub.zip
|
||||
7z x hub.zip -ohub
|
||||
;;
|
||||
*)
|
||||
echo "OS should be first parameter, was: $1"
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "$PWD/hub/bin" >> $GITHUB_PATH
|
||||
19
ci/install-rust.sh
Executable file
19
ci/install-rust.sh
Executable file
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
# Install/update rust.
|
||||
# The first argument should be the toolchain to install.
|
||||
|
||||
set -ex
|
||||
if [ -z "$1" ]
|
||||
then
|
||||
echo "First parameter must be toolchain to install."
|
||||
exit 1
|
||||
fi
|
||||
TOOLCHAIN="$1"
|
||||
|
||||
rustup set profile minimal
|
||||
rustup component remove --toolchain=$TOOLCHAIN rust-docs || echo "already removed"
|
||||
rustup update --no-self-update $TOOLCHAIN
|
||||
rustup default $TOOLCHAIN
|
||||
rustup -V
|
||||
rustc -Vv
|
||||
cargo -V
|
||||
@@ -1,47 +0,0 @@
|
||||
set -ex
|
||||
|
||||
main() {
|
||||
local target=
|
||||
if [ $TRAVIS_OS_NAME = linux ]; then
|
||||
target=x86_64-unknown-linux-musl
|
||||
sort=sort
|
||||
else
|
||||
target=x86_64-apple-darwin
|
||||
sort=gsort # for `sort --sort-version`, from brew's coreutils.
|
||||
fi
|
||||
|
||||
# Builds for iOS are done on OSX, but require the specific target to be
|
||||
# installed.
|
||||
case $TARGET in
|
||||
aarch64-apple-ios)
|
||||
rustup target install aarch64-apple-ios
|
||||
;;
|
||||
armv7-apple-ios)
|
||||
rustup target install armv7-apple-ios
|
||||
;;
|
||||
armv7s-apple-ios)
|
||||
rustup target install armv7s-apple-ios
|
||||
;;
|
||||
i386-apple-ios)
|
||||
rustup target install i386-apple-ios
|
||||
;;
|
||||
x86_64-apple-ios)
|
||||
rustup target install x86_64-apple-ios
|
||||
;;
|
||||
esac
|
||||
|
||||
# This fetches latest stable release
|
||||
local tag=$(git ls-remote --tags --refs --exit-code https://github.com/japaric/cross \
|
||||
| cut -d/ -f3 \
|
||||
| grep -E '^v[0.1.0-9.]+$' \
|
||||
| $sort --version-sort \
|
||||
| tail -n1)
|
||||
curl -LSfs https://japaric.github.io/trust/install.sh | \
|
||||
sh -s -- \
|
||||
--force \
|
||||
--git japaric/cross \
|
||||
--tag $tag \
|
||||
--target $target
|
||||
}
|
||||
|
||||
main
|
||||
47
ci/make-release.sh
Executable file
47
ci/make-release.sh
Executable file
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env bash
|
||||
# Builds the release and creates an archive and optionally deploys to GitHub.
|
||||
set -ex
|
||||
|
||||
if [[ -z "$GITHUB_REF" ]]
|
||||
then
|
||||
echo "GITHUB_REF must be set"
|
||||
exit 1
|
||||
fi
|
||||
# Strip mdbook-refs/tags/ from the start of the ref.
|
||||
TAG=${GITHUB_REF#*/tags/}
|
||||
|
||||
host=$(rustc -Vv | grep ^host: | sed -e "s/host: //g")
|
||||
export CARGO_PROFILE_RELEASE_LTO=true
|
||||
cargo build --bin mdbook --release
|
||||
cd target/release
|
||||
case $1 in
|
||||
ubuntu*)
|
||||
asset="mdbook-$TAG-$host.tar.gz"
|
||||
tar czf ../../$asset mdbook
|
||||
;;
|
||||
macos*)
|
||||
asset="mdbook-$TAG-$host.tar.gz"
|
||||
# There is a bug with BSD tar on macOS where the first 8MB of the file are
|
||||
# sometimes all NUL bytes. See https://github.com/actions/cache/issues/403
|
||||
# and https://github.com/rust-lang/cargo/issues/8603 for some more
|
||||
# information. An alternative solution here is to install GNU tar, but
|
||||
# flushing the disk cache seems to work, too.
|
||||
sudo /usr/sbin/purge
|
||||
tar czf ../../$asset mdbook
|
||||
;;
|
||||
windows*)
|
||||
asset="mdbook-$TAG-$host.zip"
|
||||
7z a ../../$asset mdbook.exe
|
||||
;;
|
||||
*)
|
||||
echo "OS should be first parameter, was: $1"
|
||||
;;
|
||||
esac
|
||||
cd ../..
|
||||
|
||||
if [[ -z "$GITHUB_TOKEN" ]]
|
||||
then
|
||||
echo "$GITHUB_TOKEN not set, skipping deploy."
|
||||
else
|
||||
hub release edit -m "" --attach $asset $TAG
|
||||
fi
|
||||
20
ci/script.sh
20
ci/script.sh
@@ -1,20 +0,0 @@
|
||||
# This script takes care of testing your crate
|
||||
|
||||
set -ex
|
||||
|
||||
main() {
|
||||
cross build --target $TARGET --all
|
||||
cross build --target $TARGET --all --release
|
||||
|
||||
if [ ! -z $DISABLE_TESTS ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
cross test --target $TARGET
|
||||
cross test --target $TARGET --release
|
||||
}
|
||||
|
||||
# we don't run the "test phase" when doing deploys
|
||||
if [ -z $TRAVIS_TAG ]; then
|
||||
main
|
||||
fi
|
||||
104
examples/nop-preprocessor.rs
Normal file
104
examples/nop-preprocessor.rs
Normal file
@@ -0,0 +1,104 @@
|
||||
use crate::nop_lib::Nop;
|
||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||
use mdbook::book::Book;
|
||||
use mdbook::errors::Error;
|
||||
use mdbook::preprocess::{CmdPreprocessor, Preprocessor, PreprocessorContext};
|
||||
use semver::{Version, VersionReq};
|
||||
use std::io;
|
||||
use std::process;
|
||||
|
||||
pub fn make_app() -> App<'static, 'static> {
|
||||
App::new("nop-preprocessor")
|
||||
.about("A mdbook preprocessor which does precisely nothing")
|
||||
.subcommand(
|
||||
SubCommand::with_name("supports")
|
||||
.arg(Arg::with_name("renderer").required(true))
|
||||
.about("Check whether a renderer is supported by this preprocessor"),
|
||||
)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let matches = make_app().get_matches();
|
||||
|
||||
// Users will want to construct their own preprocessor here
|
||||
let preprocessor = Nop::new();
|
||||
|
||||
if let Some(sub_args) = matches.subcommand_matches("supports") {
|
||||
handle_supports(&preprocessor, sub_args);
|
||||
} else if let Err(e) = handle_preprocessing(&preprocessor) {
|
||||
eprintln!("{}", e);
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_preprocessing(pre: &dyn Preprocessor) -> Result<(), Error> {
|
||||
let (ctx, book) = CmdPreprocessor::parse_input(io::stdin())?;
|
||||
|
||||
let book_version = Version::parse(&ctx.mdbook_version)?;
|
||||
let version_req = VersionReq::parse(mdbook::MDBOOK_VERSION)?;
|
||||
|
||||
if version_req.matches(&book_version) != true {
|
||||
eprintln!(
|
||||
"Warning: The {} plugin was built against version {} of mdbook, \
|
||||
but we're being called from version {}",
|
||||
pre.name(),
|
||||
mdbook::MDBOOK_VERSION,
|
||||
ctx.mdbook_version
|
||||
);
|
||||
}
|
||||
|
||||
let processed_book = pre.run(&ctx, book)?;
|
||||
serde_json::to_writer(io::stdout(), &processed_book)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_supports(pre: &dyn Preprocessor, sub_args: &ArgMatches) -> ! {
|
||||
let renderer = sub_args.value_of("renderer").expect("Required argument");
|
||||
let supported = pre.supports_renderer(&renderer);
|
||||
|
||||
// Signal whether the renderer is supported by exiting with 1 or 0.
|
||||
if supported {
|
||||
process::exit(0);
|
||||
} else {
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/// The actual implementation of the `Nop` preprocessor. This would usually go
|
||||
/// in your main `lib.rs` file.
|
||||
mod nop_lib {
|
||||
use super::*;
|
||||
|
||||
/// A no-op preprocessor.
|
||||
pub struct Nop;
|
||||
|
||||
impl Nop {
|
||||
pub fn new() -> Nop {
|
||||
Nop
|
||||
}
|
||||
}
|
||||
|
||||
impl Preprocessor for Nop {
|
||||
fn name(&self) -> &str {
|
||||
"nop-preprocessor"
|
||||
}
|
||||
|
||||
fn run(&self, ctx: &PreprocessorContext, book: Book) -> Result<Book, Error> {
|
||||
// In testing we want to tell the preprocessor to blow up by setting a
|
||||
// particular config value
|
||||
if let Some(nop_cfg) = ctx.config.get_preprocessor(self.name()) {
|
||||
if nop_cfg.contains_key("blow-up") {
|
||||
anyhow::bail!("Boom!!1!");
|
||||
}
|
||||
}
|
||||
|
||||
// we *are* a no-op preprocessor after all
|
||||
Ok(book)
|
||||
}
|
||||
|
||||
fn supports_renderer(&self, renderer: &str) -> bool {
|
||||
renderer != "not-supported"
|
||||
}
|
||||
}
|
||||
}
|
||||
30
guide/book.toml
Normal file
30
guide/book.toml
Normal file
@@ -0,0 +1,30 @@
|
||||
[book]
|
||||
title = "mdBook Documentation"
|
||||
description = "Create book from markdown files. Like Gitbook but implemented in Rust"
|
||||
authors = ["Mathieu David", "Michael-F-Bryan"]
|
||||
language = "en"
|
||||
|
||||
[rust]
|
||||
edition = "2018"
|
||||
|
||||
[output.html]
|
||||
mathjax-support = true
|
||||
site-url = "/mdBook/"
|
||||
git-repository-url = "https://github.com/rust-lang/mdBook/tree/master/guide"
|
||||
edit-url-template = "https://github.com/rust-lang/mdBook/edit/master/guide/{path}"
|
||||
|
||||
[output.html.playground]
|
||||
editable = true
|
||||
line-numbers = true
|
||||
|
||||
[output.html.search]
|
||||
limit-results = 20
|
||||
use-boolean-and = true
|
||||
boost-title = 2
|
||||
boost-hierarchy = 2
|
||||
boost-paragraph = 1
|
||||
expand = true
|
||||
heading-split-level = 2
|
||||
|
||||
[output.html.redirect]
|
||||
"/format/config.html" = "configuration/index.html"
|
||||
3
guide/src/404.md
Normal file
3
guide/src/404.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Document not found (404)
|
||||
|
||||
This URL is invalid, sorry. Try the search instead!
|
||||
25
guide/src/README.md
Normal file
25
guide/src/README.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# mdBook
|
||||
|
||||
**mdBook** is a command line tool and Rust crate to create books using Markdown
|
||||
(as by the [CommonMark](https://commonmark.org/) specification) files. It's very
|
||||
similar to Gitbook but written in [Rust](http://www.rust-lang.org).
|
||||
|
||||
What you are reading serves as an example of the output of mdBook and at the
|
||||
same time as a high-level documentation.
|
||||
|
||||
mdBook is free and open source, you can find the source code on
|
||||
[GitHub](https://github.com/rust-lang/mdBook). Issues and feature
|
||||
requests can be posted on the [GitHub issue
|
||||
tracker](https://github.com/rust-lang/mdBook/issues).
|
||||
|
||||
## API docs
|
||||
|
||||
Alongside this book you can also read the [API
|
||||
docs](https://docs.rs/mdbook/*/mdbook/) generated by Rustdoc if you would like
|
||||
to use mdBook as a crate or write a new renderer and need a more low-level
|
||||
overview.
|
||||
|
||||
## License
|
||||
|
||||
mdBook, all the source code, is released under the [Mozilla Public License
|
||||
v2.0](https://www.mozilla.org/MPL/2.0/).
|
||||
32
guide/src/SUMMARY.md
Normal file
32
guide/src/SUMMARY.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# Summary
|
||||
|
||||
- [mdBook](README.md)
|
||||
- [Command Line Tool](cli/README.md)
|
||||
- [init](cli/init.md)
|
||||
- [build](cli/build.md)
|
||||
- [watch](cli/watch.md)
|
||||
- [serve](cli/serve.md)
|
||||
- [test](cli/test.md)
|
||||
- [clean](cli/clean.md)
|
||||
- [Format](format/README.md)
|
||||
- [SUMMARY.md](format/summary.md)
|
||||
- [Draft chapter]()
|
||||
- [Configuration](format/configuration/README.md)
|
||||
- [General](format/configuration/general.md)
|
||||
- [Preprocessors](format/configuration/preprocessors.md)
|
||||
- [Renderers](format/configuration/renderers.md)
|
||||
- [Environment Variables](format/configuration/environment-variables.md)
|
||||
- [Theme](format/theme/README.md)
|
||||
- [index.hbs](format/theme/index-hbs.md)
|
||||
- [Syntax highlighting](format/theme/syntax-highlighting.md)
|
||||
- [Editor](format/theme/editor.md)
|
||||
- [MathJax Support](format/mathjax.md)
|
||||
- [mdBook-specific features](format/mdbook.md)
|
||||
- [Continuous Integration](continuous-integration.md)
|
||||
- [For Developers](for_developers/README.md)
|
||||
- [Preprocessors](for_developers/preprocessors.md)
|
||||
- [Alternative Backends](for_developers/backends.md)
|
||||
|
||||
-----------
|
||||
|
||||
[Contributors](misc/contributors.md)
|
||||
55
guide/src/cli/README.md
Normal file
55
guide/src/cli/README.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# Command Line Tool
|
||||
|
||||
mdBook can be used either as a command line tool or a [Rust
|
||||
crate](https://crates.io/crates/mdbook). Let's focus on the command line tool
|
||||
capabilities first.
|
||||
|
||||
## Install From Binaries
|
||||
|
||||
Precompiled binaries are provided for major platforms on a best-effort basis.
|
||||
Visit [the releases page](https://github.com/rust-lang/mdBook/releases)
|
||||
to download the appropriate version for your platform.
|
||||
|
||||
## Install From Source
|
||||
|
||||
mdBook can also be installed by compiling the source code on your local machine.
|
||||
|
||||
### Pre-requisite
|
||||
|
||||
mdBook is written in **[Rust](https://www.rust-lang.org/)** and therefore needs
|
||||
to be compiled with **Cargo**. If you haven't already installed Rust, please go
|
||||
ahead and [install it](https://www.rust-lang.org/tools/install) now.
|
||||
|
||||
### Install Crates.io version
|
||||
|
||||
Installing mdBook is relatively easy if you already have Rust and Cargo
|
||||
installed. You just have to type this snippet in your terminal:
|
||||
|
||||
```bash
|
||||
cargo install mdbook
|
||||
```
|
||||
|
||||
This will fetch the source code for the latest release from
|
||||
[Crates.io](https://crates.io/) and compile it. You will have to add Cargo's
|
||||
`bin` directory to your `PATH`.
|
||||
|
||||
Run `mdbook help` in your terminal to verify if it works. Congratulations, you
|
||||
have installed mdBook!
|
||||
|
||||
|
||||
### Install Git version
|
||||
|
||||
The **[git version](https://github.com/rust-lang/mdBook)** contains all
|
||||
the latest bug-fixes and features, that will be released in the next version on
|
||||
**Crates.io**, if you can't wait until the next release. You can build the git
|
||||
version yourself. Open your terminal and navigate to the directory of you
|
||||
choice. We need to clone the git repository and then build it with Cargo.
|
||||
|
||||
```bash
|
||||
git clone --depth=1 https://github.com/rust-lang/mdBook.git
|
||||
cd mdBook
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
The executable `mdbook` will be in the `./target/release` folder, this should be
|
||||
added to the path.
|
||||
40
guide/src/cli/build.md
Normal file
40
guide/src/cli/build.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# The build command
|
||||
|
||||
The build command is used to render your book:
|
||||
|
||||
```bash
|
||||
mdbook build
|
||||
```
|
||||
|
||||
It will try to parse your `SUMMARY.md` file to understand the structure of your
|
||||
book and fetch the corresponding files. Note that files mentioned in `SUMMARY.md`
|
||||
but not present will be created.
|
||||
|
||||
The rendered output will maintain the same directory structure as the source for
|
||||
convenience. Large books will therefore remain structured when rendered.
|
||||
|
||||
#### Specify a directory
|
||||
|
||||
The `build` command can take a directory as an argument to use as the book's
|
||||
root instead of the current working directory.
|
||||
|
||||
```bash
|
||||
mdbook build path/to/book
|
||||
```
|
||||
|
||||
#### --open
|
||||
|
||||
When you use the `--open` (`-o`) flag, mdbook will open the rendered book in
|
||||
your default web browser after building it.
|
||||
|
||||
#### --dest-dir
|
||||
|
||||
The `--dest-dir` (`-d`) option allows you to change the output directory for the
|
||||
book. Relative paths are interpreted relative to the book's root directory. If
|
||||
not specified it will default to the value of the `build.build-dir` key in
|
||||
`book.toml`, or to `./book`.
|
||||
|
||||
-------------------
|
||||
|
||||
***Note:*** *The build command copies all files (excluding files with `.md` extension) from the source directory
|
||||
into the build directory.*
|
||||
30
guide/src/cli/clean.md
Normal file
30
guide/src/cli/clean.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# The clean command
|
||||
|
||||
The clean command is used to delete the generated book and any other build
|
||||
artifacts.
|
||||
|
||||
```bash
|
||||
mdbook clean
|
||||
```
|
||||
|
||||
#### Specify a directory
|
||||
|
||||
The `clean` command can take a directory as an argument to use as the book's
|
||||
root instead of the current working directory.
|
||||
|
||||
```bash
|
||||
mdbook clean path/to/book
|
||||
```
|
||||
|
||||
#### --dest-dir
|
||||
|
||||
The `--dest-dir` (`-d`) option allows you to override the book's output
|
||||
directory, which will be deleted by this command. Relative paths are interpreted
|
||||
relative to the book's root directory. If not specified it will default to the
|
||||
value of the `build.build-dir` key in `book.toml`, or to `./book`.
|
||||
|
||||
```bash
|
||||
mdbook clean --dest-dir=path/to/book
|
||||
```
|
||||
|
||||
`path/to/book` could be absolute or relative.
|
||||
54
guide/src/cli/init.md
Normal file
54
guide/src/cli/init.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# The init command
|
||||
|
||||
There is some minimal boilerplate that is the same for every new book. It's for
|
||||
this purpose that mdBook includes an `init` command.
|
||||
|
||||
The `init` command is used like this:
|
||||
|
||||
```bash
|
||||
mdbook init
|
||||
```
|
||||
|
||||
When using the `init` command for the first time, a couple of files will be set
|
||||
up for you:
|
||||
```bash
|
||||
book-test/
|
||||
├── book
|
||||
└── src
|
||||
├── chapter_1.md
|
||||
└── SUMMARY.md
|
||||
```
|
||||
|
||||
- The `src` directory is where you write your book in markdown. It contains all
|
||||
the source files, configuration files, etc.
|
||||
|
||||
- The `book` directory is where your book is rendered. All the output is ready
|
||||
to be uploaded to a server to be seen by your audience.
|
||||
|
||||
- The `SUMMARY.md` is the skeleton of your
|
||||
book, and is discussed in more detail [in another
|
||||
chapter](../format/summary.md).
|
||||
|
||||
#### Tip: Generate chapters from SUMMARY.md
|
||||
|
||||
When a `SUMMARY.md` file already exists, the `init` command will first parse it
|
||||
and generate the missing files according to the paths used in the `SUMMARY.md`.
|
||||
This allows you to think and create the whole structure of your book and then
|
||||
let mdBook generate it for you.
|
||||
|
||||
#### Specify a directory
|
||||
|
||||
The `init` command can take a directory as an argument to use as the book's root
|
||||
instead of the current working directory.
|
||||
|
||||
```bash
|
||||
mdbook init path/to/book
|
||||
```
|
||||
|
||||
#### --theme
|
||||
|
||||
When you use the `--theme` flag, the default theme will be copied into a
|
||||
directory called `theme` in your source directory so that you can modify it.
|
||||
|
||||
The theme is selectively overwritten, this means that if you don't want to
|
||||
overwrite a specific file, just delete it and the default file will be used.
|
||||
56
guide/src/cli/serve.md
Normal file
56
guide/src/cli/serve.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# The serve command
|
||||
|
||||
The serve command is used to preview a book by serving it via HTTP at
|
||||
`localhost:3000` by default:
|
||||
|
||||
```bash
|
||||
mdbook serve
|
||||
```
|
||||
|
||||
The `serve` command watches the book's `src` directory for
|
||||
changes, rebuilding the book and refreshing clients for each change; this includes
|
||||
re-creating deleted files still mentioned in `SUMMARY.md`! A websocket
|
||||
connection is used to trigger the client-side refresh.
|
||||
|
||||
***Note:*** *The `serve` command is for testing a book's HTML output, and is not
|
||||
intended to be a complete HTTP server for a website.*
|
||||
|
||||
#### Specify a directory
|
||||
|
||||
The `serve` command can take a directory as an argument to use as the book's
|
||||
root instead of the current working directory.
|
||||
|
||||
```bash
|
||||
mdbook serve path/to/book
|
||||
```
|
||||
|
||||
### Server options
|
||||
|
||||
The `serve` hostname defaults to `localhost`, and the port defaults to `3000`. Either option can be specified on the command line:
|
||||
|
||||
```bash
|
||||
mdbook serve path/to/book -p 8000 -n 127.0.0.1
|
||||
```
|
||||
|
||||
#### --open
|
||||
|
||||
When you use the `--open` (`-o`) flag, mdbook will open the book in your
|
||||
default web browser after starting the server.
|
||||
|
||||
#### --dest-dir
|
||||
|
||||
The `--dest-dir` (`-d`) option allows you to change the output directory for the
|
||||
book. Relative paths are interpreted relative to the book's root directory. If
|
||||
not specified it will default to the value of the `build.build-dir` key in
|
||||
`book.toml`, or to `./book`.
|
||||
|
||||
#### Specify exclude patterns
|
||||
|
||||
The `serve` command will not automatically trigger a build for files listed in
|
||||
the `.gitignore` file in the book root directory. The `.gitignore` file may
|
||||
contain file patterns described in the [gitignore
|
||||
documentation](https://git-scm.com/docs/gitignore). This can be useful for
|
||||
ignoring temporary files created by some editors.
|
||||
|
||||
***Note:*** *Only the `.gitignore` from the book root directory is used. Global
|
||||
`$HOME/.gitignore` or `.gitignore` files in parent directories are not used.*
|
||||
53
guide/src/cli/test.md
Normal file
53
guide/src/cli/test.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# The test command
|
||||
|
||||
When writing a book, you sometimes need to automate some tests. For example,
|
||||
[The Rust Programming Book](https://doc.rust-lang.org/stable/book/) uses a lot
|
||||
of code examples that could get outdated. Therefore it is very important for
|
||||
them to be able to automatically test these code examples.
|
||||
|
||||
mdBook supports a `test` command that will run all available tests in a book. At
|
||||
the moment, only rustdoc tests are supported, but this may be expanded upon in
|
||||
the future.
|
||||
|
||||
#### Disable tests on a code block
|
||||
|
||||
rustdoc doesn't test code blocks which contain the `ignore` attribute:
|
||||
|
||||
```rust,ignore
|
||||
fn main() {}
|
||||
```
|
||||
|
||||
rustdoc also doesn't test code blocks which specify a language other than Rust:
|
||||
|
||||
```markdown
|
||||
**Foo**: _bar_
|
||||
```
|
||||
|
||||
rustdoc *does* test code blocks which have no language specified:
|
||||
|
||||
```
|
||||
This is going to cause an error!
|
||||
```
|
||||
|
||||
#### Specify a directory
|
||||
|
||||
The `test` command can take a directory as an argument to use as the book's root
|
||||
instead of the current working directory.
|
||||
|
||||
```bash
|
||||
mdbook test path/to/book
|
||||
```
|
||||
|
||||
#### --library-path
|
||||
|
||||
The `--library-path` (`-L`) option allows you to add directories to the library
|
||||
search path used by `rustdoc` when it builds and tests the examples. Multiple
|
||||
directories can be specified with multiple options (`-L foo -L bar`) or with a
|
||||
comma-delimited list (`-L foo,bar`).
|
||||
|
||||
#### --dest-dir
|
||||
|
||||
The `--dest-dir` (`-d`) option allows you to change the output directory for the
|
||||
book. Relative paths are interpreted relative to the book's root directory. If
|
||||
not specified it will default to the value of the `build.build-dir` key in
|
||||
`book.toml`, or to `./book`.
|
||||
40
guide/src/cli/watch.md
Normal file
40
guide/src/cli/watch.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# The watch command
|
||||
|
||||
The `watch` command is useful when you want your book to be rendered on every
|
||||
file change. You could repeatedly issue `mdbook build` every time a file is
|
||||
changed. But using `mdbook watch` once will watch your files and will trigger a
|
||||
build automatically whenever you modify a file; this includes re-creating
|
||||
deleted files still mentioned in `SUMMARY.md`!
|
||||
|
||||
#### Specify a directory
|
||||
|
||||
The `watch` command can take a directory as an argument to use as the book's
|
||||
root instead of the current working directory.
|
||||
|
||||
```bash
|
||||
mdbook watch path/to/book
|
||||
```
|
||||
|
||||
#### --open
|
||||
|
||||
When you use the `--open` (`-o`) option, mdbook will open the rendered book in
|
||||
your default web browser.
|
||||
|
||||
#### --dest-dir
|
||||
|
||||
The `--dest-dir` (`-d`) option allows you to change the output directory for the
|
||||
book. Relative paths are interpreted relative to the book's root directory. If
|
||||
not specified it will default to the value of the `build.build-dir` key in
|
||||
`book.toml`, or to `./book`.
|
||||
|
||||
|
||||
#### Specify exclude patterns
|
||||
|
||||
The `watch` command will not automatically trigger a build for files listed in
|
||||
the `.gitignore` file in the book root directory. The `.gitignore` file may
|
||||
contain file patterns described in the [gitignore
|
||||
documentation](https://git-scm.com/docs/gitignore). This can be useful for
|
||||
ignoring temporary files created by some editors.
|
||||
|
||||
_Note: Only `.gitignore` from book root directory is used. Global
|
||||
`$HOME/.gitignore` or `.gitignore` files in parent directories are not used._
|
||||
154
guide/src/continuous-integration.md
Normal file
154
guide/src/continuous-integration.md
Normal file
@@ -0,0 +1,154 @@
|
||||
# Running `mdbook` in Continuous Integration
|
||||
|
||||
While the following examples use Travis CI, their principles should
|
||||
straightforwardly transfer to other continuous integration providers as well.
|
||||
|
||||
## Ensuring Your Book Builds and Tests Pass
|
||||
|
||||
Here is a sample Travis CI `.travis.yml` configuration that ensures `mdbook
|
||||
build` and `mdbook test` run successfully. The key to fast CI turnaround times
|
||||
is caching `mdbook` installs, so that you aren't compiling `mdbook` on every CI
|
||||
run.
|
||||
|
||||
```yaml
|
||||
language: rust
|
||||
sudo: false
|
||||
|
||||
cache:
|
||||
- cargo
|
||||
|
||||
rust:
|
||||
- stable
|
||||
|
||||
before_script:
|
||||
- (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update)
|
||||
- (test -x $HOME/.cargo/bin/mdbook || cargo install --vers "^0.3" mdbook)
|
||||
- cargo install-update -a
|
||||
|
||||
script:
|
||||
- mdbook build path/to/mybook && mdbook test path/to/mybook
|
||||
```
|
||||
|
||||
## Deploying Your Book to GitHub Pages
|
||||
|
||||
Following these instructions will result in your book being published to GitHub
|
||||
pages after a successful CI run on your repository's `master` branch.
|
||||
|
||||
First, create a new GitHub "Personal Access Token" with the "public_repo"
|
||||
permissions (or "repo" for private repositories). Go to your repository's Travis
|
||||
CI settings page and add an environment variable named `GITHUB_TOKEN` that is
|
||||
marked secure and *not* shown in the logs.
|
||||
|
||||
Whilst still in your repository's settings page, navigate to Options and change the
|
||||
Source on GitHub pages to `gh-pages`.
|
||||
|
||||
Then, append this snippet to your `.travis.yml` and update the path to the
|
||||
`book` directory:
|
||||
|
||||
```yaml
|
||||
deploy:
|
||||
provider: pages
|
||||
skip-cleanup: true
|
||||
github-token: $GITHUB_TOKEN
|
||||
local-dir: path/to/mybook/book
|
||||
keep-history: false
|
||||
on:
|
||||
branch: master
|
||||
```
|
||||
|
||||
That's it!
|
||||
|
||||
Note: Travis has a new [dplv2](https://blog.travis-ci.com/2019-08-27-deployment-tooling-dpl-v2-preview-release) configuration that is currently in beta. To use this new format, update your `.travis.yml` file to:
|
||||
|
||||
```yaml
|
||||
language: rust
|
||||
os: linux
|
||||
dist: xenial
|
||||
|
||||
cache:
|
||||
- cargo
|
||||
|
||||
rust:
|
||||
- stable
|
||||
|
||||
before_script:
|
||||
- (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update)
|
||||
- (test -x $HOME/.cargo/bin/mdbook || cargo install --vers "^0.3" mdbook)
|
||||
- cargo install-update -a
|
||||
|
||||
script:
|
||||
- mdbook build path/to/mybook && mdbook test path/to/mybook
|
||||
|
||||
deploy:
|
||||
provider: pages
|
||||
strategy: git
|
||||
edge: true
|
||||
cleanup: false
|
||||
github-token: $GITHUB_TOKEN
|
||||
local-dir: path/to/mybook/book
|
||||
keep-history: false
|
||||
on:
|
||||
branch: master
|
||||
target_branch: gh-pages
|
||||
```
|
||||
|
||||
### Deploying to GitHub Pages manually
|
||||
|
||||
If your CI doesn't support GitHub pages, or you're deploying somewhere else
|
||||
with integrations such as Github Pages:
|
||||
*note: you may want to use different tmp dirs*:
|
||||
|
||||
```console
|
||||
$> git worktree add /tmp/book gh-pages
|
||||
$> mdbook build
|
||||
$> rm -rf /tmp/book/* # this won't delete the .git directory
|
||||
$> cp -rp book/* /tmp/book/
|
||||
$> cd /tmp/book
|
||||
$> git add -A
|
||||
$> git commit 'new book message'
|
||||
$> git push origin gh-pages
|
||||
$> cd -
|
||||
```
|
||||
|
||||
Or put this into a Makefile rule:
|
||||
|
||||
```makefile
|
||||
.PHONY: deploy
|
||||
deploy: book
|
||||
@echo "====> deploying to github"
|
||||
git worktree add /tmp/book gh-pages
|
||||
rm -rf /tmp/book/*
|
||||
cp -rp book/* /tmp/book/
|
||||
cd /tmp/book && \
|
||||
git add -A && \
|
||||
git commit -m "deployed on $(shell date) by ${USER}" && \
|
||||
git push origin gh-pages
|
||||
```
|
||||
|
||||
## Deploying Your Book to GitLab Pages
|
||||
Inside your repository's project root, create a file named `.gitlab-ci.yml` with the following contents:
|
||||
```yml
|
||||
stages:
|
||||
- deploy
|
||||
|
||||
pages:
|
||||
stage: deploy
|
||||
image: rust
|
||||
variables:
|
||||
CARGO_HOME: $CI_PROJECT_DIR/cargo
|
||||
before_script:
|
||||
- export PATH="$PATH:$CARGO_HOME/bin"
|
||||
- mdbook --version || cargo install mdbook
|
||||
script:
|
||||
- mdbook build -d public
|
||||
only:
|
||||
- master
|
||||
artifacts:
|
||||
paths:
|
||||
- public
|
||||
cache:
|
||||
paths:
|
||||
- $CARGO_HOME/bin
|
||||
```
|
||||
|
||||
After you commit and push this new file, GitLab CI will run and your book will be available!
|
||||
@@ -1,25 +1,25 @@
|
||||
# For Developers
|
||||
|
||||
While `mdbook` is mainly used as a command line tool, you can also import the
|
||||
While `mdbook` is mainly used as a command line tool, you can also import the
|
||||
underlying library directly and use that to manage a book. It also has a fairly
|
||||
flexible plugin mechanism, allowing you to create your own custom tooling and
|
||||
flexible plugin mechanism, allowing you to create your own custom tooling and
|
||||
consumers (often referred to as *backends*) if you need to do some analysis of
|
||||
the book or render it in a different format.
|
||||
|
||||
The *For Developers* chapters are here to show you the more advanced usage of
|
||||
The *For Developers* chapters are here to show you the more advanced usage of
|
||||
`mdbook`.
|
||||
|
||||
The two main ways a developer can hook into the book's build process is via,
|
||||
|
||||
- [Preprocessors](for_developers/preprocessors.html)
|
||||
- [Alternate Backends](for_developers/backends.html)
|
||||
- [Preprocessors](preprocessors.md)
|
||||
- [Alternative Backends](backends.md)
|
||||
|
||||
|
||||
## The Build Process
|
||||
|
||||
The process of rendering a book project goes through several steps.
|
||||
|
||||
1. Load the book
|
||||
1. Load the book
|
||||
- Parse the `book.toml`, falling back to the default `Config` if it doesn't
|
||||
exist
|
||||
- Load the book chapters into memory
|
||||
@@ -32,15 +32,15 @@ The process of rendering a book project goes through several steps.
|
||||
|
||||
The `mdbook` binary is just a wrapper around the `mdbook` crate, exposing its
|
||||
functionality as a command-line program. As such it is quite easy to create your
|
||||
own programs which use `mdbook` internally, adding your own functionality (e.g.
|
||||
own programs which use `mdbook` internally, adding your own functionality (e.g.
|
||||
a custom preprocessor) or tweaking the build process.
|
||||
|
||||
The easiest way to find out how to use the `mdbook` crate is by looking at the
|
||||
[API Docs]. The top level documentation explains how one would use the
|
||||
[API Docs]. The top level documentation explains how one would use the
|
||||
[`MDBook`] type to load and build a book, while the [config] module gives a good
|
||||
explanation on the configuration system.
|
||||
|
||||
|
||||
[`MDBook`]: http://rust-lang-nursery.github.io/mdBook/mdbook/book/struct.MDBook.html
|
||||
[API Docs]: http://rust-lang-nursery.github.io/mdBook/mdbook/
|
||||
[config]: file:///home/michael/Documents/forks/mdBook/target/doc/mdbook/config/index.html
|
||||
[`MDBook`]: https://docs.rs/mdbook/*/mdbook/book/struct.MDBook.html
|
||||
[API Docs]: https://docs.rs/mdbook/*/mdbook/
|
||||
[config]: https://docs.rs/mdbook/*/mdbook/config/index.html
|
||||
@@ -1,11 +1,11 @@
|
||||
# Alternate Backends
|
||||
# Alternative Backends
|
||||
|
||||
A "backend" is simply a program which `mdbook` will invoke during the book
|
||||
A "backend" is simply a program which `mdbook` will invoke during the book
|
||||
rendering process. This program is passed a JSON representation of the book and
|
||||
configuration information via `stdin`. Once the backend receives this
|
||||
configuration information via `stdin`. Once the backend receives this
|
||||
information it is free to do whatever it wants.
|
||||
|
||||
There are already several alternate backends on GitHub which can be used as a
|
||||
There are already several alternative backends on GitHub which can be used as a
|
||||
rough example of how this is accomplished in practice.
|
||||
|
||||
- [mdbook-linkcheck] - a simple program for verifying the book doesn't contain
|
||||
@@ -14,24 +14,24 @@ rough example of how this is accomplished in practice.
|
||||
- [mdbook-test] - a program to run the book's contents through [rust-skeptic] to
|
||||
verify everything compiles and runs correctly (similar to `rustdoc --test`)
|
||||
|
||||
This page will step you through creating your own alternate backend in the form
|
||||
This page will step you through creating your own alternative backend in the form
|
||||
of a simple word counting program. Although it will be written in Rust, there's
|
||||
no reason why it couldn't be accomplished using something like Python or Ruby.
|
||||
|
||||
|
||||
## Setting Up
|
||||
|
||||
First you'll want to create a new binary program and add `mdbook` as a
|
||||
First you'll want to create a new binary program and add `mdbook` as a
|
||||
dependency.
|
||||
|
||||
```
|
||||
```shell
|
||||
$ cargo new --bin mdbook-wordcount
|
||||
$ cd mdbook-wordcount
|
||||
$ cd mdbook-wordcount
|
||||
$ cargo add mdbook
|
||||
```
|
||||
|
||||
When our `mdbook-wordcount` plugin is invoked, `mdbook` will send it a JSON
|
||||
version of [`RenderContext`] via our plugin's `stdin`. For convenience, there's
|
||||
When our `mdbook-wordcount` plugin is invoked, `mdbook` will send it a JSON
|
||||
version of [`RenderContext`] via our plugin's `stdin`. For convenience, there's
|
||||
a [`RenderContext::from_json()`] constructor which will load a `RenderContext`.
|
||||
|
||||
This is all the boilerplate necessary for our backend to load the book.
|
||||
@@ -49,18 +49,18 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
> **Note:** The `RenderContext` contains a `version` field. This lets backends
|
||||
> **Note:** The `RenderContext` contains a `version` field. This lets backends
|
||||
figure out whether they are compatible with the version of `mdbook` it's being
|
||||
called by. This `version` comes directly from the corresponding field in
|
||||
`mdbook`'s `Cargo.toml`.
|
||||
|
||||
called by. This `version` comes directly from the corresponding field in
|
||||
`mdbook`'s `Cargo.toml`.
|
||||
|
||||
It is recommended that backends use the [`semver`] crate to inspect this field
|
||||
and emit a warning if there may be a compatibility issue.
|
||||
|
||||
|
||||
## Inspecting the Book
|
||||
|
||||
Now our backend has a copy of the book, lets count how many words are in each
|
||||
Now our backend has a copy of the book, lets count how many words are in each
|
||||
chapter!
|
||||
|
||||
Because the `RenderContext` contains a [`Book`] field (`book`), and a `Book` has
|
||||
@@ -89,15 +89,15 @@ fn count_words(ch: &Chapter) -> usize {
|
||||
|
||||
## Enabling the Backend
|
||||
|
||||
Now we've got the basics running, we want to actually use it. First, install
|
||||
the program.
|
||||
Now we've got the basics running, we want to actually use it. First, install the
|
||||
program.
|
||||
|
||||
```
|
||||
$ cargo install
|
||||
```shell
|
||||
$ cargo install --path .
|
||||
```
|
||||
|
||||
Then `cd` to the particular book you'd like to count the words of and update its
|
||||
`book.toml` file.
|
||||
`book.toml` file.
|
||||
|
||||
```diff
|
||||
[book]
|
||||
@@ -110,18 +110,17 @@ Then `cd` to the particular book you'd like to count the words of and update its
|
||||
+ [output.wordcount]
|
||||
```
|
||||
|
||||
When it loads a book into memory, `mdbook` will inspect your `book.toml` file
|
||||
to try and figure out which backends to use by looking for all `output.*`
|
||||
tables. If none are provided it'll fall back to using the default HTML
|
||||
renderer.
|
||||
When it loads a book into memory, `mdbook` will inspect your `book.toml` file to
|
||||
try and figure out which backends to use by looking for all `output.*` tables.
|
||||
If none are provided it'll fall back to using the default HTML renderer.
|
||||
|
||||
Notably, this means if you want to add your own custom backend you'll also
|
||||
need to make sure to add the HTML backend, even if its table just stays empty.
|
||||
Notably, this means if you want to add your own custom backend you'll also need
|
||||
to make sure to add the HTML backend, even if its table just stays empty.
|
||||
|
||||
Now you just need to build your book like normal, and everything should *Just
|
||||
Now you just need to build your book like normal, and everything should *Just
|
||||
Work*.
|
||||
|
||||
```
|
||||
```shell
|
||||
$ mdbook build
|
||||
...
|
||||
2018-01-16 07:31:15 [INFO] (mdbook::renderer): Invoking the "mdbook-wordcount" renderer
|
||||
@@ -141,15 +140,15 @@ Syntax highlighting: 314
|
||||
MathJax Support: 153
|
||||
Rust code specific features: 148
|
||||
For Developers: 788
|
||||
Alternate Backends: 710
|
||||
Alternative Backends: 710
|
||||
Contributors: 85
|
||||
```
|
||||
|
||||
The reason we didn't need to specify the full name/path of our `wordcount`
|
||||
backend is because `mdbook` will try to *infer* the program's name via
|
||||
convention. The executable for the `foo` backend is typically called
|
||||
The reason we didn't need to specify the full name/path of our `wordcount`
|
||||
backend is because `mdbook` will try to *infer* the program's name via
|
||||
convention. The executable for the `foo` backend is typically called
|
||||
`mdbook-foo`, with an associated `[output.foo]` entry in the `book.toml`. To
|
||||
explicitly tell `mdbook` what command to invoke (it may require command-line
|
||||
explicitly tell `mdbook` what command to invoke (it may require command-line
|
||||
arguments or be an interpreted script), you can use the `command` field.
|
||||
|
||||
```diff
|
||||
@@ -168,16 +167,16 @@ arguments or be an interpreted script), you can use the `command` field.
|
||||
## Configuration
|
||||
|
||||
Now imagine you don't want to count the number of words on a particular chapter
|
||||
(it might be generated text/code, etc). The canonical way to do this is via
|
||||
the usual `book.toml` configuration file by adding items to your `[output.foo]`
|
||||
table.
|
||||
(it might be generated text/code, etc). The canonical way to do this is via the
|
||||
usual `book.toml` configuration file by adding items to your `[output.foo]`
|
||||
table.
|
||||
|
||||
The `Config` can be treated roughly as a nested hashmap which lets you call
|
||||
methods like `get()` to access the config's contents, with a
|
||||
`get_deserialized()` convenience method for retrieving a value and
|
||||
automatically deserializing to some arbitrary type `T`.
|
||||
`get_deserialized()` convenience method for retrieving a value and automatically
|
||||
deserializing to some arbitrary type `T`.
|
||||
|
||||
To implement this, we'll create our own serializable `WordcountConfig` struct
|
||||
To implement this, we'll create our own serializable `WordcountConfig` struct
|
||||
which will encapsulate all configuration for this backend.
|
||||
|
||||
First add `serde` and `serde_derive` to your `Cargo.toml`,
|
||||
@@ -212,13 +211,13 @@ and then add a check to make sure we skip ignored chapters.
|
||||
+ let cfg: WordcountConfig = ctx.config
|
||||
+ .get_deserialized("output.wordcount")
|
||||
+ .unwrap_or_default();
|
||||
|
||||
|
||||
for item in ctx.book.iter() {
|
||||
if let BookItem::Chapter(ref ch) = *item {
|
||||
+ if cfg.ignores.contains(&ch.name) {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+
|
||||
let num_words = count_words(ch);
|
||||
println!("{}: {}", ch.name, num_words);
|
||||
}
|
||||
@@ -229,7 +228,7 @@ and then add a check to make sure we skip ignored chapters.
|
||||
|
||||
## Output and Signalling Failure
|
||||
|
||||
While it's nice to print word counts to the terminal when a book is built, it
|
||||
While it's nice to print word counts to the terminal when a book is built, it
|
||||
might also be a good idea to output them to a file somewhere. `mdbook` tells a
|
||||
backend where it should place any generated output via the `destination` field
|
||||
in [`RenderContext`].
|
||||
@@ -240,17 +239,17 @@ in [`RenderContext`].
|
||||
- use std::io;
|
||||
use mdbook::renderer::RenderContext;
|
||||
use mdbook::book::{BookItem, Chapter};
|
||||
|
||||
|
||||
fn main() {
|
||||
...
|
||||
|
||||
|
||||
+ let _ = fs::create_dir_all(&ctx.destination);
|
||||
+ let mut f = File::create(ctx.destination.join("wordcounts.txt")).unwrap();
|
||||
+
|
||||
+
|
||||
for item in ctx.book.iter() {
|
||||
if let BookItem::Chapter(ref ch) = *item {
|
||||
...
|
||||
|
||||
|
||||
let num_words = count_words(ch);
|
||||
println!("{}: {}", ch.name, num_words);
|
||||
+ writeln!(f, "{}: {}", ch.name, num_words).unwrap();
|
||||
@@ -262,9 +261,13 @@ in [`RenderContext`].
|
||||
> **Note:** There is no guarantee that the destination directory exists or is
|
||||
> empty (`mdbook` may leave the previous contents to let backends do caching),
|
||||
> so it's always a good idea to create it with `fs::create_dir_all()`.
|
||||
>
|
||||
> If the destination directory already exists, don't assume it will be empty.
|
||||
> To allow backends to cache the results from previous runs, `mdbook` may leave
|
||||
> old content in the directory.
|
||||
|
||||
There's always the possibility that an error will occur while processing a book
|
||||
(just look at all the `unwrap()`'s we've written already), so `mdbook` will
|
||||
(just look at all the `unwrap()`'s we've written already), so `mdbook` will
|
||||
interpret a non-zero exit code as a rendering failure.
|
||||
|
||||
For example, if we wanted to make sure all chapters have an *even* number of
|
||||
@@ -277,11 +280,11 @@ like this:
|
||||
|
||||
fn main() {
|
||||
...
|
||||
|
||||
|
||||
for item in ctx.book.iter() {
|
||||
if let BookItem::Chapter(ref ch) = *item {
|
||||
...
|
||||
|
||||
|
||||
let num_words = count_words(ch);
|
||||
println!("{}: {}", ch.name, num_words);
|
||||
writeln!(f, "{}: {}", ch.name, num_words).unwrap();
|
||||
@@ -304,8 +307,8 @@ like this:
|
||||
|
||||
Now, if we reinstall the backend and build a book,
|
||||
|
||||
```
|
||||
$ cargo install --force
|
||||
```shell
|
||||
$ cargo install --path . --force
|
||||
$ mdbook build /path/to/book
|
||||
...
|
||||
2018-01-16 21:21:39 [INFO] (mdbook::renderer): Invoking the "wordcount" renderer
|
||||
@@ -315,27 +318,59 @@ init: 283
|
||||
init has an odd number of words!
|
||||
2018-01-16 21:21:39 [ERROR] (mdbook::renderer): Renderer exited with non-zero return code.
|
||||
2018-01-16 21:21:39 [ERROR] (mdbook::utils): Error: Rendering failed
|
||||
2018-01-16 21:21:39 [ERROR] (mdbook::utils): Caused By: The "mdbook-wordcount" renderer failed
|
||||
2018-01-16 21:21:39 [ERROR] (mdbook::utils): Caused By: The "mdbook-wordcount" renderer failed
|
||||
```
|
||||
|
||||
As you've probably already noticed, output from the plugin's subprocess is
|
||||
immediately passed through to the user. It is encouraged for plugins to
|
||||
follow the "rule of silence" and only generate output when necessary (e.g. an
|
||||
error in generation or a warning).
|
||||
immediately passed through to the user. It is encouraged for plugins to follow
|
||||
the "rule of silence" and only generate output when necessary (e.g. an error in
|
||||
generation or a warning).
|
||||
|
||||
All environment variables are passed through to the backend, allowing you to
|
||||
use the usual `RUST_LOG` to control logging verbosity.
|
||||
All environment variables are passed through to the backend, allowing you to use
|
||||
the usual `RUST_LOG` to control logging verbosity.
|
||||
|
||||
## Handling missing backends
|
||||
|
||||
If you enable a backend that isn't installed, the default behavior is to throw an error:
|
||||
|
||||
```text
|
||||
The command `mdbook-wordcount` wasn't found, is the "wordcount" backend installed?
|
||||
If you want to ignore this error when the "wordcount" backend is not installed,
|
||||
set `optional = true` in the `[output.wordcount]` section of the book.toml configuration file.
|
||||
```
|
||||
|
||||
This behavior can be changed by marking the backend as optional.
|
||||
|
||||
```diff
|
||||
[book]
|
||||
title = "mdBook Documentation"
|
||||
description = "Create book from markdown files. Like Gitbook but implemented in Rust"
|
||||
authors = ["Mathieu David", "Michael-F-Bryan"]
|
||||
|
||||
[output.html]
|
||||
|
||||
[output.wordcount]
|
||||
command = "python /path/to/wordcount.py"
|
||||
+ optional = true
|
||||
```
|
||||
|
||||
This demotes the error to a warning, and it will instead look like this:
|
||||
|
||||
```text
|
||||
The command was not found, but was marked as optional.
|
||||
Command: wordcount
|
||||
```
|
||||
|
||||
|
||||
## Wrapping Up
|
||||
|
||||
Although contrived, hopefully this example was enough to show how you'd create
|
||||
an alternate backend for `mdbook`. If you feel it's missing something, don't
|
||||
an alternative backend for `mdbook`. If you feel it's missing something, don't
|
||||
hesitate to create an issue in the [issue tracker] so we can improve the user
|
||||
guide.
|
||||
|
||||
The existing backends mentioned towards the start of this chapter should serve
|
||||
as a good example of how it's done in real life, so feel free to skim through
|
||||
as a good example of how it's done in real life, so feel free to skim through
|
||||
the source code or ask questions.
|
||||
|
||||
|
||||
@@ -343,10 +378,10 @@ the source code or ask questions.
|
||||
[mdbook-epub]: https://github.com/Michael-F-Bryan/mdbook-epub
|
||||
[mdbook-test]: https://github.com/Michael-F-Bryan/mdbook-test
|
||||
[rust-skeptic]: https://github.com/budziq/rust-skeptic
|
||||
[`RenderContext`]: http://rust-lang-nursery.github.io/mdBook/mdbook/renderer/struct.RenderContext.html
|
||||
[`RenderContext::from_json()`]: http://rust-lang-nursery.github.io/mdBook/mdbook/renderer/struct.RenderContext.html#method.from_json
|
||||
[`RenderContext`]: https://docs.rs/mdbook/*/mdbook/renderer/struct.RenderContext.html
|
||||
[`RenderContext::from_json()`]: https://docs.rs/mdbook/*/mdbook/renderer/struct.RenderContext.html#method.from_json
|
||||
[`semver`]: https://crates.io/crates/semver
|
||||
[`Book`]: http://rust-lang-nursery.github.io/mdBook/mdbook/book/struct.Book.html
|
||||
[`Book::iter()`]: http://rust-lang-nursery.github.io/mdBook/mdbook/book/struct.Book.html#method.iter
|
||||
[`Config`]: http://rust-lang-nursery.github.io/mdBook/mdbook/config/struct.Config.html
|
||||
[issue tracker]: https://github.com/rust-lang-nursery/mdBook/issues
|
||||
[`Book`]: https://docs.rs/mdbook/*/mdbook/book/struct.Book.html
|
||||
[`Book::iter()`]: https://docs.rs/mdbook/*/mdbook/book/struct.Book.html#method.iter
|
||||
[`Config`]: https://docs.rs/mdbook/*/mdbook/config/struct.Config.html
|
||||
[issue tracker]: https://github.com/rust-lang/mdBook/issues
|
||||
115
guide/src/for_developers/preprocessors.md
Normal file
115
guide/src/for_developers/preprocessors.md
Normal file
@@ -0,0 +1,115 @@
|
||||
# Preprocessors
|
||||
|
||||
A *preprocessor* is simply a bit of code which gets run immediately after the
|
||||
book is loaded and before it gets rendered, allowing you to update and mutate
|
||||
the book. Possible use cases are:
|
||||
|
||||
- Creating custom helpers like `\{{#include /path/to/file.md}}`
|
||||
- Updating links so `[some chapter](some_chapter.md)` is automatically changed
|
||||
to `[some chapter](some_chapter.html)` for the HTML renderer
|
||||
- Substituting in latex-style expressions (`$$ \frac{1}{3} $$`) with their
|
||||
mathjax equivalents
|
||||
|
||||
|
||||
## Hooking Into MDBook
|
||||
|
||||
MDBook uses a fairly simple mechanism for discovering third party plugins.
|
||||
A new table is added to `book.toml` (e.g. `preprocessor.foo` for the `foo`
|
||||
preprocessor) and then `mdbook` will try to invoke the `mdbook-foo` program as
|
||||
part of the build process.
|
||||
|
||||
A preprocessor can be hard-coded to specify which backend(s) it should be run
|
||||
for with the `preprocessor.foo.renderer` key. For example, it doesn't make sense for
|
||||
[MathJax](../format/mathjax.md) to be used for non-HTML renderers.
|
||||
|
||||
```toml
|
||||
[book]
|
||||
title = "My Book"
|
||||
authors = ["Michael-F-Bryan"]
|
||||
|
||||
[preprocessor.foo]
|
||||
# The command can also be specified manually
|
||||
command = "python3 /path/to/foo.py"
|
||||
# Only run the `foo` preprocessor for the HTML and EPUB renderer
|
||||
renderer = ["html", "epub"]
|
||||
```
|
||||
|
||||
In typical unix style, all inputs to the plugin will be written to `stdin` as
|
||||
JSON and `mdbook` will read from `stdout` if it is expecting output.
|
||||
|
||||
The easiest way to get started is by creating your own implementation of the
|
||||
`Preprocessor` trait (e.g. in `lib.rs`) and then creating a shell binary which
|
||||
translates inputs to the correct `Preprocessor` method. For convenience, there
|
||||
is [an example no-op preprocessor] in the `examples/` directory which can easily
|
||||
be adapted for other preprocessors.
|
||||
|
||||
<details>
|
||||
<summary>Example no-op preprocessor</summary>
|
||||
|
||||
```rust
|
||||
// nop-preprocessors.rs
|
||||
|
||||
{{#include ../../../examples/nop-preprocessor.rs}}
|
||||
```
|
||||
</details>
|
||||
|
||||
## Hints For Implementing A Preprocessor
|
||||
|
||||
By pulling in `mdbook` as a library, preprocessors can have access to the
|
||||
existing infrastructure for dealing with books.
|
||||
|
||||
For example, a custom preprocessor could use the
|
||||
[`CmdPreprocessor::parse_input()`] function to deserialize the JSON written to
|
||||
`stdin`. Then each chapter of the `Book` can be mutated in-place via
|
||||
[`Book::for_each_mut()`], and then written to `stdout` with the `serde_json`
|
||||
crate.
|
||||
|
||||
Chapters can be accessed either directly (by recursively iterating over
|
||||
chapters) or via the `Book::for_each_mut()` convenience method.
|
||||
|
||||
The `chapter.content` is just a string which happens to be markdown. While it's
|
||||
entirely possible to use regular expressions or do a manual find & replace,
|
||||
you'll probably want to process the input into something more computer-friendly.
|
||||
The [`pulldown-cmark`][pc] crate implements a production-quality event-based
|
||||
Markdown parser, with the [`pulldown-cmark-to-cmark`][pctc] allowing you to
|
||||
translate events back into markdown text.
|
||||
|
||||
The following code block shows how to remove all emphasis from markdown,
|
||||
without accidentally breaking the document.
|
||||
|
||||
```rust
|
||||
fn remove_emphasis(
|
||||
num_removed_items: &mut usize,
|
||||
chapter: &mut Chapter,
|
||||
) -> Result<String> {
|
||||
let mut buf = String::with_capacity(chapter.content.len());
|
||||
|
||||
let events = Parser::new(&chapter.content).filter(|e| {
|
||||
let should_keep = match *e {
|
||||
Event::Start(Tag::Emphasis)
|
||||
| Event::Start(Tag::Strong)
|
||||
| Event::End(Tag::Emphasis)
|
||||
| Event::End(Tag::Strong) => false,
|
||||
_ => true,
|
||||
};
|
||||
if !should_keep {
|
||||
*num_removed_items += 1;
|
||||
}
|
||||
should_keep
|
||||
});
|
||||
|
||||
cmark(events, &mut buf, None).map(|_| buf).map_err(|err| {
|
||||
Error::from(format!("Markdown serialization failed: {}", err))
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
For everything else, have a look [at the complete example][example].
|
||||
|
||||
[preprocessor-docs]: https://docs.rs/mdbook/latest/mdbook/preprocess/trait.Preprocessor.html
|
||||
[pc]: https://crates.io/crates/pulldown-cmark
|
||||
[pctc]: https://crates.io/crates/pulldown-cmark-to-cmark
|
||||
[example]: https://github.com/rust-lang/mdBook/blob/master/examples/nop-preprocessor.rs
|
||||
[an example no-op preprocessor]: https://github.com/rust-lang/mdBook/blob/master/examples/nop-preprocessor.rs
|
||||
[`CmdPreprocessor::parse_input()`]: https://docs.rs/mdbook/latest/mdbook/preprocess/trait.Preprocessor.html#method.parse_input
|
||||
[`Book::for_each_mut()`]: https://docs.rs/mdbook/latest/mdbook/book/struct.Book.html#method.for_each_mut
|
||||
12
guide/src/format/configuration/README.md
Normal file
12
guide/src/format/configuration/README.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# Configuration
|
||||
|
||||
This section details the configuration options available in the ***book.toml***:
|
||||
- **[General]** configuration including the `book`, `rust`, `build` sections
|
||||
- **[Preprocessor]** configuration for default and custom book preprocessors
|
||||
- **[Renderer]** configuration for the HTML, Markdown and custom renderers
|
||||
- **[Environment Variable]** configuration for overriding configuration options in your environment
|
||||
|
||||
[General]: general.md
|
||||
[Preprocessor]: preprocessors.md
|
||||
[Renderer]: renderers.md
|
||||
[Environment Variable]: environment-variables.md
|
||||
38
guide/src/format/configuration/environment-variables.md
Normal file
38
guide/src/format/configuration/environment-variables.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# Environment Variables
|
||||
|
||||
All configuration values can be overridden from the command line by setting the
|
||||
corresponding environment variable. Because many operating systems restrict
|
||||
environment variables to be alphanumeric characters or `_`, the configuration
|
||||
key needs to be formatted slightly differently to the normal `foo.bar.baz` form.
|
||||
|
||||
Variables starting with `MDBOOK_` are used for configuration. The key is created
|
||||
by removing the `MDBOOK_` prefix and turning the resulting string into
|
||||
`kebab-case`. Double underscores (`__`) separate nested keys, while a single
|
||||
underscore (`_`) is replaced with a dash (`-`).
|
||||
|
||||
For example:
|
||||
|
||||
- `MDBOOK_foo` -> `foo`
|
||||
- `MDBOOK_FOO` -> `foo`
|
||||
- `MDBOOK_FOO__BAR` -> `foo.bar`
|
||||
- `MDBOOK_FOO_BAR` -> `foo-bar`
|
||||
- `MDBOOK_FOO_bar__baz` -> `foo-bar.baz`
|
||||
|
||||
So by setting the `MDBOOK_BOOK__TITLE` environment variable you can override the
|
||||
book's title without needing to touch your `book.toml`.
|
||||
|
||||
> **Note:** To facilitate setting more complex config items, the value of an
|
||||
> environment variable is first parsed as JSON, falling back to a string if the
|
||||
> parse fails.
|
||||
>
|
||||
> This means, if you so desired, you could override all book metadata when
|
||||
> building the book with something like
|
||||
>
|
||||
> ```shell
|
||||
> $ export MDBOOK_BOOK="{'title': 'My Awesome Book', authors: ['Michael-F-Bryan']}"
|
||||
> $ mdbook build
|
||||
> ```
|
||||
|
||||
The latter case may be useful in situations where `mdbook` is invoked from a
|
||||
script or CI, where it sometimes isn't possible to update the `book.toml` before
|
||||
building.
|
||||
97
guide/src/format/configuration/general.md
Normal file
97
guide/src/format/configuration/general.md
Normal file
@@ -0,0 +1,97 @@
|
||||
# General Configuration
|
||||
|
||||
You can configure the parameters for your book in the ***book.toml*** file.
|
||||
|
||||
Here is an example of what a ***book.toml*** file might look like:
|
||||
|
||||
```toml
|
||||
[book]
|
||||
title = "Example book"
|
||||
author = "John Doe"
|
||||
description = "The example book covers examples."
|
||||
|
||||
[rust]
|
||||
edition = "2018"
|
||||
|
||||
[build]
|
||||
build-dir = "my-example-book"
|
||||
create-missing = false
|
||||
|
||||
[preprocessor.index]
|
||||
|
||||
[preprocessor.links]
|
||||
|
||||
[output.html]
|
||||
additional-css = ["custom.css"]
|
||||
|
||||
[output.html.search]
|
||||
limit-results = 15
|
||||
```
|
||||
|
||||
## Supported configuration options
|
||||
|
||||
It is important to note that **any** relative path specified in the
|
||||
configuration will always be taken relative from the root of the book where the
|
||||
configuration file is located.
|
||||
|
||||
### General metadata
|
||||
|
||||
This is general information about your book.
|
||||
|
||||
- **title:** The title of the book
|
||||
- **authors:** The author(s) of the book
|
||||
- **description:** A description for the book, which is added as meta
|
||||
information in the html `<head>` of each page
|
||||
- **src:** By default, the source directory is found in the directory named
|
||||
`src` directly under the root folder. But this is configurable with the `src`
|
||||
key in the configuration file.
|
||||
- **language:** The main language of the book, which is used as a language attribute `<html lang="en">` for example.
|
||||
|
||||
**book.toml**
|
||||
```toml
|
||||
[book]
|
||||
title = "Example book"
|
||||
authors = ["John Doe", "Jane Doe"]
|
||||
description = "The example book covers examples."
|
||||
src = "my-src" # the source files will be found in `root/my-src` instead of `root/src`
|
||||
language = "en"
|
||||
```
|
||||
|
||||
### Rust options
|
||||
|
||||
Options for the Rust language, relevant to running tests and playground
|
||||
integration.
|
||||
|
||||
- **edition**: Rust edition to use by default for the code snippets. Default
|
||||
is "2015". Individual code blocks can be controlled with the `edition2015`
|
||||
or `edition2018` annotations, such as:
|
||||
|
||||
~~~text
|
||||
```rust,edition2015
|
||||
// This only works in 2015.
|
||||
let try = true;
|
||||
```
|
||||
~~~
|
||||
|
||||
### Build options
|
||||
|
||||
This controls the build process of your book.
|
||||
|
||||
- **build-dir:** The directory to put the rendered book in. By default this is
|
||||
`book/` in the book's root directory.
|
||||
- **create-missing:** By default, any missing files specified in `SUMMARY.md`
|
||||
will be created when the book is built (i.e. `create-missing = true`). If this
|
||||
is `false` then the build process will instead exit with an error if any files
|
||||
do not exist.
|
||||
- **use-default-preprocessors:** Disable the default preprocessors of (`links` &
|
||||
`index`) by setting this option to `false`.
|
||||
|
||||
If you have the same, and/or other preprocessors declared via their table
|
||||
of configuration, they will run instead.
|
||||
|
||||
- For clarity, with no preprocessor configuration, the default `links` and
|
||||
`index` will run.
|
||||
- Setting `use-default-preprocessors = false` will disable these
|
||||
default preprocessors from running.
|
||||
- Adding `[preprocessor.links]`, for example, will ensure, regardless of
|
||||
`use-default-preprocessors` that `links` it will run.
|
||||
58
guide/src/format/configuration/preprocessors.md
Normal file
58
guide/src/format/configuration/preprocessors.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# Configuring Preprocessors
|
||||
|
||||
The following preprocessors are available and included by default:
|
||||
|
||||
- `links`: Expand the `{{ #playground }}`, `{{ #include }}`, and `{{ #rustdoc_include }}` handlebars
|
||||
helpers in a chapter to include the contents of a file.
|
||||
- `index`: Convert all chapter files named `README.md` into `index.md`. That is
|
||||
to say, all `README.md` would be rendered to an index file `index.html` in the
|
||||
rendered book.
|
||||
|
||||
|
||||
**book.toml**
|
||||
```toml
|
||||
[build]
|
||||
build-dir = "build"
|
||||
create-missing = false
|
||||
|
||||
[preprocessor.links]
|
||||
|
||||
[preprocessor.index]
|
||||
```
|
||||
|
||||
### Custom Preprocessor Configuration
|
||||
|
||||
Like renderers, preprocessor will need to be given its own table (e.g.
|
||||
`[preprocessor.mathjax]`). In the section, you may then pass extra
|
||||
configuration to the preprocessor by adding key-value pairs to the table.
|
||||
|
||||
For example
|
||||
|
||||
```toml
|
||||
[preprocessor.links]
|
||||
# set the renderers this preprocessor will run for
|
||||
renderers = ["html"]
|
||||
some_extra_feature = true
|
||||
```
|
||||
|
||||
#### Locking a Preprocessor dependency to a renderer
|
||||
|
||||
You can explicitly specify that a preprocessor should run for a renderer by
|
||||
binding the two together.
|
||||
|
||||
```toml
|
||||
[preprocessor.mathjax]
|
||||
renderers = ["html"] # mathjax only makes sense with the HTML renderer
|
||||
```
|
||||
|
||||
### Provide Your Own Command
|
||||
|
||||
By default when you add a `[preprocessor.foo]` table to your `book.toml` file,
|
||||
`mdbook` will try to invoke the `mdbook-foo` executable. If you want to use a
|
||||
different program name or pass in command-line arguments, this behaviour can
|
||||
be overridden by adding a `command` field.
|
||||
|
||||
```toml
|
||||
[preprocessor.random]
|
||||
command = "python random.py"
|
||||
```
|
||||
209
guide/src/format/configuration/renderers.md
Normal file
209
guide/src/format/configuration/renderers.md
Normal file
@@ -0,0 +1,209 @@
|
||||
# Configuring Renderers
|
||||
|
||||
### HTML renderer options
|
||||
|
||||
The HTML renderer has a couple of options as well. All the options for the
|
||||
renderer need to be specified under the TOML table `[output.html]`.
|
||||
|
||||
The following configuration options are available:
|
||||
|
||||
- **theme:** mdBook comes with a default theme and all the resource files needed
|
||||
for it. But if this option is set, mdBook will selectively overwrite the theme
|
||||
files with the ones found in the specified folder.
|
||||
- **default-theme:** The theme color scheme to select by default in the
|
||||
'Change Theme' dropdown. Defaults to `light`.
|
||||
- **preferred-dark-theme:** The default dark theme. This theme will be used if
|
||||
the browser requests the dark version of the site via the
|
||||
['prefers-color-scheme'](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme)
|
||||
CSS media query. Defaults to `navy`.
|
||||
- **curly-quotes:** Convert straight quotes to curly quotes, except for those
|
||||
that occur in code blocks and code spans. Defaults to `false`.
|
||||
- **mathjax-support:** Adds support for [MathJax](../mathjax.md). Defaults to
|
||||
`false`.
|
||||
- **copy-fonts:** Copies fonts.css and respective font files to the output directory and use them in the default theme. Defaults to `true`.
|
||||
- **google-analytics:** If you use Google Analytics, this option lets you enable
|
||||
it by simply specifying your ID in the configuration file.
|
||||
- **additional-css:** If you need to slightly change the appearance of your book
|
||||
without overwriting the whole style, you can specify a set of stylesheets that
|
||||
will be loaded after the default ones where you can surgically change the
|
||||
style.
|
||||
- **additional-js:** If you need to add some behaviour to your book without
|
||||
removing the current behaviour, you can specify a set of JavaScript files that
|
||||
will be loaded alongside the default one.
|
||||
- **print:** A subtable for configuration print settings. mdBook by default adds
|
||||
support for printing out the book as a single page. This is accessed using the
|
||||
print icon on the top right of the book.
|
||||
- **no-section-label:** mdBook by defaults adds section label in table of
|
||||
contents column. For example, "1.", "2.1". Set this option to true to disable
|
||||
those labels. Defaults to `false`.
|
||||
- **fold:** A subtable for configuring sidebar section-folding behavior.
|
||||
- **playground:** A subtable for configuring various playground settings.
|
||||
- **search:** A subtable for configuring the in-browser search functionality.
|
||||
mdBook must be compiled with the `search` feature enabled (on by default).
|
||||
- **git-repository-url:** A url to the git repository for the book. If provided
|
||||
an icon link will be output in the menu bar of the book.
|
||||
- **git-repository-icon:** The FontAwesome icon class to use for the git
|
||||
repository link. Defaults to `fa-github`.
|
||||
- **edit-url-template:** Edit url template, when provided shows a
|
||||
"Suggest an edit" button for directly jumping to editing the currently
|
||||
viewed page. For e.g. GitHub projects set this to
|
||||
`https://github.com/<owner>/<repo>/edit/master/{path}` or for
|
||||
Bitbucket projects set it to
|
||||
`https://bitbucket.org/<owner>/<repo>/src/master/{path}?mode=edit`
|
||||
where {path} will be replaced with the full path of the file in the
|
||||
repository.
|
||||
- **redirect:** A subtable used for generating redirects when a page is moved.
|
||||
The table contains key-value pairs where the key is where the redirect file
|
||||
needs to be created, as an absolute path from the build directory, (e.g.
|
||||
`/appendices/bibliography.html`). The value can be any valid URI the
|
||||
browser should navigate to (e.g. `https://rust-lang.org/`,
|
||||
`/overview.html`, or `../bibliography.html`).
|
||||
- **input-404:** The name of the markdown file used for missing files.
|
||||
The corresponding output file will be the same, with the extension replaced with `html`.
|
||||
Defaults to `404.md`.
|
||||
- **site-url:** The url where the book will be hosted. This is required to ensure
|
||||
navigation links and script/css imports in the 404 file work correctly, even when accessing
|
||||
urls in subdirectories. Defaults to `/`.
|
||||
- **cname:** The DNS subdomain or apex domain at which your book will be hosted.
|
||||
This string will be written to a file named CNAME in the root of your site, as
|
||||
required by GitHub Pages (see [*Managing a custom domain for your GitHub Pages
|
||||
site*][custom domain]).
|
||||
|
||||
[custom domain]: https://docs.github.com/en/github/working-with-github-pages/managing-a-custom-domain-for-your-github-pages-site
|
||||
|
||||
Available configuration options for the `[output.html.print]` table:
|
||||
|
||||
- **enable:** Enable print support. When `false`, all print support will not be
|
||||
rendered. Defaults to `true`.
|
||||
|
||||
Available configuration options for the `[output.html.fold]` table:
|
||||
|
||||
- **enable:** Enable section-folding. When off, all folds are open.
|
||||
Defaults to `false`.
|
||||
- **level:** The higher the more folded regions are open. When level is 0, all
|
||||
folds are closed. Defaults to `0`.
|
||||
|
||||
Available configuration options for the `[output.html.playground]` table:
|
||||
|
||||
- **editable:** Allow editing the source code. Defaults to `false`.
|
||||
- **copyable:** Display the copy button on code snippets. Defaults to `true`.
|
||||
- **copy-js:** Copy JavaScript files for the editor to the output directory.
|
||||
Defaults to `true`.
|
||||
- **line-numbers** Display line numbers on editable sections of code. Requires both `editable` and `copy-js` to be `true`. Defaults to `false`.
|
||||
|
||||
[Ace]: https://ace.c9.io/
|
||||
|
||||
Available configuration options for the `[output.html.search]` table:
|
||||
|
||||
- **enable:** Enables the search feature. Defaults to `true`.
|
||||
- **limit-results:** The maximum number of search results. Defaults to `30`.
|
||||
- **teaser-word-count:** The number of words used for a search result teaser.
|
||||
Defaults to `30`.
|
||||
- **use-boolean-and:** Define the logical link between multiple search words. If
|
||||
true, all search words must appear in each result. Defaults to `false`.
|
||||
- **boost-title:** Boost factor for the search result score if a search word
|
||||
appears in the header. Defaults to `2`.
|
||||
- **boost-hierarchy:** Boost factor for the search result score if a search word
|
||||
appears in the hierarchy. The hierarchy contains all titles of the parent
|
||||
documents and all parent headings. Defaults to `1`.
|
||||
- **boost-paragraph:** Boost factor for the search result score if a search word
|
||||
appears in the text. Defaults to `1`.
|
||||
- **expand:** True if search should match longer results e.g. search `micro`
|
||||
should match `microwave`. Defaults to `true`.
|
||||
- **heading-split-level:** Search results will link to a section of the document
|
||||
which contains the result. Documents are split into sections by headings this
|
||||
level or less. Defaults to `3`. (`### This is a level 3 heading`)
|
||||
- **copy-js:** Copy JavaScript files for the search implementation to the output
|
||||
directory. Defaults to `true`.
|
||||
|
||||
This shows all available HTML output options in the **book.toml**:
|
||||
|
||||
```toml
|
||||
[book]
|
||||
title = "Example book"
|
||||
authors = ["John Doe", "Jane Doe"]
|
||||
description = "The example book covers examples."
|
||||
|
||||
[output.html]
|
||||
theme = "my-theme"
|
||||
default-theme = "light"
|
||||
preferred-dark-theme = "navy"
|
||||
curly-quotes = true
|
||||
mathjax-support = false
|
||||
copy-fonts = true
|
||||
google-analytics = "UA-123456-7"
|
||||
additional-css = ["custom.css", "custom2.css"]
|
||||
additional-js = ["custom.js"]
|
||||
no-section-label = false
|
||||
git-repository-url = "https://github.com/rust-lang/mdBook"
|
||||
git-repository-icon = "fa-github"
|
||||
edit-url-template = "https://github.com/rust-lang/mdBook/edit/master/guide/{path}"
|
||||
site-url = "/example-book/"
|
||||
cname = "myproject.rs"
|
||||
input-404 = "not-found.md"
|
||||
|
||||
[output.html.print]
|
||||
enable = true
|
||||
|
||||
[output.html.fold]
|
||||
enable = false
|
||||
level = 0
|
||||
|
||||
[output.html.playground]
|
||||
editable = false
|
||||
copy-js = true
|
||||
line-numbers = false
|
||||
|
||||
[output.html.search]
|
||||
enable = true
|
||||
limit-results = 30
|
||||
teaser-word-count = 30
|
||||
use-boolean-and = true
|
||||
boost-title = 2
|
||||
boost-hierarchy = 1
|
||||
boost-paragraph = 1
|
||||
expand = true
|
||||
heading-split-level = 3
|
||||
copy-js = true
|
||||
|
||||
[output.html.redirect]
|
||||
"/appendices/bibliography.html" = "https://rustc-dev-guide.rust-lang.org/appendix/bibliography.html"
|
||||
"/other-installation-methods.html" = "../infra/other-installation-methods.html"
|
||||
```
|
||||
|
||||
### Markdown Renderer
|
||||
|
||||
The Markdown renderer will run preprocessors and then output the resulting
|
||||
Markdown. This is mostly useful for debugging preprocessors, especially in
|
||||
conjunction with `mdbook test` to see the Markdown that `mdbook` is passing
|
||||
to `rustdoc`.
|
||||
|
||||
The Markdown renderer is included with `mdbook` but disabled by default.
|
||||
Enable it by adding an empty table to your `book.toml` as follows:
|
||||
|
||||
```toml
|
||||
[output.markdown]
|
||||
```
|
||||
|
||||
There are no configuration options for the Markdown renderer at this time;
|
||||
only whether it is enabled or disabled.
|
||||
|
||||
See [the preprocessors documentation](#configuring-preprocessors) for how to
|
||||
specify which preprocessors should run before the Markdown renderer.
|
||||
|
||||
### Custom Renderers
|
||||
|
||||
A custom renderer can be enabled by adding a `[output.foo]` table to your
|
||||
`book.toml`. Similar to [preprocessors](#configuring-preprocessors) this will
|
||||
instruct `mdbook` to pass a representation of the book to `mdbook-foo` for
|
||||
rendering. See the [alternative backends] chapter for more detail.
|
||||
|
||||
The custom renderer has access to all the fields within its table (i.e.
|
||||
anything under `[output.foo]`). mdBook checks for two common fields:
|
||||
|
||||
- **command:** The command to execute for this custom renderer. Defaults to
|
||||
the name of the renderer with the `mdbook-` prefix (such as `mdbook-foo`).
|
||||
- **optional:** If `true`, then the command will be ignored if it is not
|
||||
installed, otherwise mdBook will fail with an error. Defaults to `false`.
|
||||
|
||||
[alternative backends]: ../../for_developers/backends.md
|
||||
6
guide/src/format/example.rs
Normal file
6
guide/src/format/example.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
fn main() {
|
||||
println!("Hello World!");
|
||||
#
|
||||
# // You can even hide lines! :D
|
||||
# println!("I am hidden! Expand the code snippet to see me");
|
||||
}
|
||||
43
guide/src/format/mathjax.md
Normal file
43
guide/src/format/mathjax.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# MathJax Support
|
||||
|
||||
mdBook has optional support for math equations through
|
||||
[MathJax](https://www.mathjax.org/).
|
||||
|
||||
To enable MathJax, you need to add the `mathjax-support` key to your `book.toml`
|
||||
under the `output.html` section.
|
||||
|
||||
```toml
|
||||
[output.html]
|
||||
mathjax-support = true
|
||||
```
|
||||
|
||||
>**Note:** The usual delimiters MathJax uses are not yet supported. You can't
|
||||
currently use `$$ ... $$` as delimiters and the `\[ ... \]` delimiters need an
|
||||
extra backslash to work. Hopefully this limitation will be lifted soon.
|
||||
|
||||
>**Note:** When you use double backslashes in MathJax blocks (for example in
|
||||
> commands such as `\begin{cases} \frac 1 2 \\ \frac 3 4 \end{cases}`) you need
|
||||
> to add _two extra_ backslashes (e.g., `\begin{cases} \frac 1 2 \\\\ \frac 3 4
|
||||
> \end{cases}`).
|
||||
|
||||
|
||||
### Inline equations
|
||||
Inline equations are delimited by `\\(` and `\\)`. So for example, to render the
|
||||
following inline equation \\( \int x dx = \frac{x^2}{2} + C \\) you would write
|
||||
the following:
|
||||
```
|
||||
\\( \int x dx = \frac{x^2}{2} + C \\)
|
||||
```
|
||||
|
||||
### Block equations
|
||||
Block equations are delimited by `\\[` and `\\]`. To render the following
|
||||
equation
|
||||
|
||||
\\[ \mu = \frac{1}{N} \sum_{i=0} x_i \\]
|
||||
|
||||
|
||||
you would write:
|
||||
|
||||
```bash
|
||||
\\[ \mu = \frac{1}{N} \sum_{i=0} x_i \\]
|
||||
```
|
||||
203
guide/src/format/mdbook.md
Normal file
203
guide/src/format/mdbook.md
Normal file
@@ -0,0 +1,203 @@
|
||||
# mdBook-specific features
|
||||
|
||||
## Hiding code lines
|
||||
|
||||
There is a feature in mdBook that lets you hide code lines by prepending them
|
||||
with a `#` [like you would with Rustdoc][rustdoc-hide].
|
||||
|
||||
[rustdoc-hide]: https://doc.rust-lang.org/stable/rustdoc/documentation-tests.html#hiding-portions-of-the-example
|
||||
|
||||
```bash
|
||||
# fn main() {
|
||||
let x = 5;
|
||||
let y = 6;
|
||||
|
||||
println!("{}", x + y);
|
||||
# }
|
||||
```
|
||||
|
||||
Will render as
|
||||
|
||||
```rust
|
||||
# fn main() {
|
||||
let x = 5;
|
||||
let y = 7;
|
||||
|
||||
println!("{}", x + y);
|
||||
# }
|
||||
```
|
||||
|
||||
## Including files
|
||||
|
||||
With the following syntax, you can include files into your book:
|
||||
|
||||
```hbs
|
||||
\{{#include file.rs}}
|
||||
```
|
||||
|
||||
The path to the file has to be relative from the current source file.
|
||||
|
||||
mdBook will interpret included files as Markdown. Since the include command
|
||||
is usually used for inserting code snippets and examples, you will often
|
||||
wrap the command with ```` ``` ```` to display the file contents without
|
||||
interpreting them.
|
||||
|
||||
````hbs
|
||||
```
|
||||
\{{#include file.rs}}
|
||||
```
|
||||
````
|
||||
|
||||
## Including portions of a file
|
||||
Often you only need a specific part of the file, e.g. relevant lines for an
|
||||
example. We support four different modes of partial includes:
|
||||
|
||||
```hbs
|
||||
\{{#include file.rs:2}}
|
||||
\{{#include file.rs::10}}
|
||||
\{{#include file.rs:2:}}
|
||||
\{{#include file.rs:2:10}}
|
||||
```
|
||||
|
||||
The first command only includes the second line from file `file.rs`. The second
|
||||
command includes all lines up to line 10, i.e. the lines from 11 till the end of
|
||||
the file are omitted. The third command includes all lines from line 2, i.e. the
|
||||
first line is omitted. The last command includes the excerpt of `file.rs`
|
||||
consisting of lines 2 to 10.
|
||||
|
||||
To avoid breaking your book when modifying included files, you can also
|
||||
include a specific section using anchors instead of line numbers.
|
||||
An anchor is a pair of matching lines. The line beginning an anchor must
|
||||
match the regex `ANCHOR:\s*[\w_-]+` and similarly the ending line must match
|
||||
the regex `ANCHOR_END:\s*[\w_-]+`. This allows you to put anchors in
|
||||
any kind of commented line.
|
||||
|
||||
Consider the following file to include:
|
||||
```rs
|
||||
/* ANCHOR: all */
|
||||
|
||||
// ANCHOR: component
|
||||
struct Paddle {
|
||||
hello: f32,
|
||||
}
|
||||
// ANCHOR_END: component
|
||||
|
||||
////////// ANCHOR: system
|
||||
impl System for MySystem { ... }
|
||||
////////// ANCHOR_END: system
|
||||
|
||||
/* ANCHOR_END: all */
|
||||
```
|
||||
|
||||
Then in the book, all you have to do is:
|
||||
````hbs
|
||||
Here is a component:
|
||||
```rust,no_run,noplayground
|
||||
\{{#include file.rs:component}}
|
||||
```
|
||||
|
||||
Here is a system:
|
||||
```rust,no_run,noplayground
|
||||
\{{#include file.rs:system}}
|
||||
```
|
||||
|
||||
This is the full file.
|
||||
```rust,no_run,noplayground
|
||||
\{{#include file.rs:all}}
|
||||
```
|
||||
````
|
||||
|
||||
Lines containing anchor patterns inside the included anchor are ignored.
|
||||
|
||||
## Including a file but initially hiding all except specified lines
|
||||
|
||||
The `rustdoc_include` helper is for including code from external Rust files that contain complete
|
||||
examples, but only initially showing particular lines specified with line numbers or anchors in the
|
||||
same way as with `include`.
|
||||
|
||||
The lines not in the line number range or between the anchors will still be included, but they will
|
||||
be prefaced with `#`. This way, a reader can expand the snippet to see the complete example, and
|
||||
Rustdoc will use the complete example when you run `mdbook test`.
|
||||
|
||||
For example, consider a file named `file.rs` that contains this Rust program:
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let x = add_one(2);
|
||||
assert_eq!(x, 3);
|
||||
}
|
||||
|
||||
fn add_one(num: i32) -> i32 {
|
||||
num + 1
|
||||
}
|
||||
```
|
||||
|
||||
We can include a snippet that initially shows only line 2 by using this syntax:
|
||||
|
||||
````hbs
|
||||
To call the `add_one` function, we pass it an `i32` and bind the returned value to `x`:
|
||||
|
||||
```rust
|
||||
\{{#rustdoc_include file.rs:2}}
|
||||
```
|
||||
````
|
||||
|
||||
This would have the same effect as if we had manually inserted the code and hidden all but line 2
|
||||
using `#`:
|
||||
|
||||
````hbs
|
||||
To call the `add_one` function, we pass it an `i32` and bind the returned value to `x`:
|
||||
|
||||
```rust
|
||||
# fn main() {
|
||||
let x = add_one(2);
|
||||
# assert_eq!(x, 3);
|
||||
# }
|
||||
#
|
||||
# fn add_one(num: i32) -> i32 {
|
||||
# num + 1
|
||||
# }
|
||||
```
|
||||
````
|
||||
|
||||
That is, it looks like this (click the "expand" icon to see the rest of the file):
|
||||
|
||||
```rust
|
||||
# fn main() {
|
||||
let x = add_one(2);
|
||||
# assert_eq!(x, 3);
|
||||
# }
|
||||
#
|
||||
# fn add_one(num: i32) -> i32 {
|
||||
# num + 1
|
||||
# }
|
||||
```
|
||||
|
||||
## Inserting runnable Rust files
|
||||
|
||||
With the following syntax, you can insert runnable Rust files into your book:
|
||||
|
||||
```hbs
|
||||
\{{#playground file.rs}}
|
||||
```
|
||||
|
||||
The path to the Rust file has to be relative from the current source file.
|
||||
|
||||
When play is clicked, the code snippet will be sent to the [Rust Playground] to be
|
||||
compiled and run. The result is sent back and displayed directly underneath the
|
||||
code.
|
||||
|
||||
Here is what a rendered code snippet looks like:
|
||||
|
||||
{{#playground example.rs}}
|
||||
|
||||
[Rust Playground]: https://play.rust-lang.org/
|
||||
|
||||
## Controlling page \<title\>
|
||||
|
||||
A chapter can set a \<title\> that is different from its entry in the table of
|
||||
contents (sidebar) by including a `\{{#title ...}}` near the top of the page.
|
||||
|
||||
```hbs
|
||||
\{{#title My Title}}
|
||||
```
|
||||
99
guide/src/format/summary.md
Normal file
99
guide/src/format/summary.md
Normal file
@@ -0,0 +1,99 @@
|
||||
# SUMMARY.md
|
||||
|
||||
The summary file is used by mdBook to know what chapters to include, in what
|
||||
order they should appear, what their hierarchy is and where the source files
|
||||
are. Without this file, there is no book.
|
||||
|
||||
This markdown file must be named `SUMMARY.md`. Its formatting
|
||||
is very strict and must follow the structure outlined below to allow for easy
|
||||
parsing. Any element not specified below, be it formatting or textual, is likely
|
||||
to be ignored at best, or may cause an error when attempting to build the book.
|
||||
|
||||
### Structure
|
||||
|
||||
1. ***Title*** - While optional, it's common practice to begin with a title, generally <code
|
||||
class="language-markdown"># Summary</code>. This is ignored by the parser however, and
|
||||
can be omitted.
|
||||
```markdown
|
||||
# Summary
|
||||
```
|
||||
|
||||
1. ***Prefix Chapter*** - Before the main numbered chapters, prefix chapters can be added
|
||||
that will not be numbered. This is useful for forewords,
|
||||
introductions, etc. There are, however, some constraints. Prefix chapters cannot be
|
||||
nested; they should all be on the root level. And you can not add
|
||||
prefix chapters once you have added numbered chapters.
|
||||
```markdown
|
||||
[A Prefix Chapter](relative/path/to/markdown.md)
|
||||
|
||||
- [First Chapter](relative/path/to/markdown2.md)
|
||||
```
|
||||
|
||||
1. ***Part Title*** - Headers can be used as a title for the following numbered
|
||||
chapters. This can be used to logically separate different sections
|
||||
of the book. The title is rendered as unclickable text.
|
||||
Titles are optional, and the numbered chapters can be broken into as many
|
||||
parts as desired.
|
||||
```markdown
|
||||
# My Part Tile
|
||||
|
||||
- [First Chapter](relative/path/to/markdown.md)
|
||||
```
|
||||
|
||||
1. ***Numbered Chapter*** - Numbered chapters outline the main content of the book
|
||||
and can be nested, resulting in a nice hierarchy
|
||||
(chapters, sub-chapters, etc.).
|
||||
```markdown
|
||||
# Title of Part
|
||||
|
||||
- [First Chapter](relative/path/to/markdown.md)
|
||||
- [Second Chapter](relative/path/to/markdown2.md)
|
||||
- [Sub Chapter](relative/path/to/markdown3.md)
|
||||
|
||||
# Title of Another Part
|
||||
|
||||
- [Another Chapter](relative/path/to/markdown4.md)
|
||||
```
|
||||
Numbered chapters can be denoted with either `-` or `*` (do not mix delimiters).
|
||||
|
||||
1. ***Suffix Chapter*** - Like prefix chapters, suffix chapters are unnumbered, but they come after
|
||||
numbered chapters.
|
||||
```markdown
|
||||
- [Last Chapter](relative/path/to/markdown.md)
|
||||
|
||||
[Title of Suffix Chapter](relative/path/to/markdown2.md)
|
||||
```
|
||||
|
||||
1. ***Draft chapters*** - Draft chapters are chapters without a file and thus content.
|
||||
The purpose of a draft chapter is to signal future chapters still to be written.
|
||||
Or when still laying out the structure of the book to avoid creating the files
|
||||
while you are still changing the structure of the book a lot.
|
||||
Draft chapters will be rendered in the HTML renderer as disabled links in the table
|
||||
of contents, as you can see for the next chapter in the table of contents on the left.
|
||||
Draft chapters are written like normal chapters but without writing the path to the file.
|
||||
```markdown
|
||||
- [Draft Chapter]()
|
||||
```
|
||||
|
||||
1. ***Separators*** - Separators can be added before, in between, and after any other element. They result
|
||||
in an HTML rendered line in the built table of contents. A separator is
|
||||
a line containing exclusively dashes and at least three of them: `---`.
|
||||
```markdown
|
||||
# My Part Title
|
||||
|
||||
[A Prefix Chapter](relative/path/to/markdown.md)
|
||||
|
||||
---
|
||||
|
||||
- [First Chapter](relative/path/to/markdown2.md)
|
||||
```
|
||||
|
||||
|
||||
### Example
|
||||
|
||||
Below is the markdown source for the `SUMMARY.md` for this guide, with the resulting table
|
||||
of contents as rendered to the left.
|
||||
|
||||
```markdown
|
||||
{{#include ../SUMMARY.md}}
|
||||
```
|
||||
46
guide/src/format/theme/README.md
Normal file
46
guide/src/format/theme/README.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# Theme
|
||||
|
||||
The default renderer uses a [handlebars](http://handlebarsjs.com/) template to
|
||||
render your markdown files and comes with a default theme included in the mdBook
|
||||
binary.
|
||||
|
||||
The theme is totally customizable, you can selectively replace every file from
|
||||
the theme by your own by adding a `theme` directory next to `src` folder in your
|
||||
project root. Create a new file with the name of the file you want to override
|
||||
and now that file will be used instead of the default file.
|
||||
|
||||
Here are the files you can override:
|
||||
|
||||
- **_index.hbs_** is the handlebars template.
|
||||
- **_head.hbs_** is appended to the HTML `<head>` section.
|
||||
- **_header.hbs_** content is appended on top of every book page.
|
||||
- **_css/_** contains the CSS files for styling the book.
|
||||
- **_css/chrome.css_** is for UI elements.
|
||||
- **_css/general.css_** is the base styles.
|
||||
- **_css/print.css_** is the style for printer output.
|
||||
- **_css/variables.css_** contains variables used in other CSS files.
|
||||
- **_book.js_** is mostly used to add client side functionality, like hiding /
|
||||
un-hiding the sidebar, changing the theme, ...
|
||||
- **_highlight.js_** is the JavaScript that is used to highlight code snippets,
|
||||
you should not need to modify this.
|
||||
- **_highlight.css_** is the theme used for the code highlighting.
|
||||
- **_favicon.svg_** and **_favicon.png_** the favicon that will be used. The SVG
|
||||
version is used by [newer browsers].
|
||||
|
||||
Generally, when you want to tweak the theme, you don't need to override all the
|
||||
files. If you only need changes in the stylesheet, there is no point in
|
||||
overriding all the other files. Because custom files take precedence over
|
||||
built-in ones, they will not get updated with new fixes / features.
|
||||
|
||||
**Note:** When you override a file, it is possible that you break some
|
||||
functionality. Therefore I recommend to use the file from the default theme as
|
||||
template and only add / modify what you need. You can copy the default theme
|
||||
into your source directory automatically by using `mdbook init --theme` just
|
||||
remove the files you don't want to override.
|
||||
|
||||
If you completely replace all built-in themes, be sure to also set
|
||||
[`output.html.preferred-dark-theme`] in the config, which defaults to the
|
||||
built-in `navy` theme.
|
||||
|
||||
[`output.html.preferred-dark-theme`]: ../configuration/renderers.md#html-renderer-options
|
||||
[newer browsers]: https://caniuse.com/#feat=link-icon-svg
|
||||
46
guide/src/format/theme/editor.md
Normal file
46
guide/src/format/theme/editor.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# Editor
|
||||
|
||||
In addition to providing runnable code playgrounds, mdBook optionally allows them
|
||||
to be editable. In order to enable editable code blocks, the following needs to
|
||||
be added to the ***book.toml***:
|
||||
|
||||
```toml
|
||||
[output.html.playground]
|
||||
editable = true
|
||||
```
|
||||
|
||||
To make a specific block available for editing, the attribute `editable` needs
|
||||
to be added to it:
|
||||
|
||||
<pre><code class="language-markdown">```rust,editable
|
||||
fn main() {
|
||||
let number = 5;
|
||||
print!("{}", number);
|
||||
}
|
||||
```</code></pre>
|
||||
|
||||
The above will result in this editable playground:
|
||||
|
||||
```rust,editable
|
||||
fn main() {
|
||||
let number = 5;
|
||||
print!("{}", number);
|
||||
}
|
||||
```
|
||||
|
||||
Note the new `Undo Changes` button in the editable playgrounds.
|
||||
|
||||
## Customizing the Editor
|
||||
|
||||
By default, the editor is the [Ace](https://ace.c9.io/) editor, but, if desired,
|
||||
the functionality may be overridden by providing a different folder:
|
||||
|
||||
```toml
|
||||
[output.html.playground]
|
||||
editable = true
|
||||
editor = "/path/to/editor"
|
||||
```
|
||||
|
||||
Note that for the editor changes to function correctly, the `book.js` inside of
|
||||
the `theme` folder will need to be overridden as it has some couplings with the
|
||||
default Ace editor.
|
||||
101
guide/src/format/theme/index-hbs.md
Normal file
101
guide/src/format/theme/index-hbs.md
Normal file
@@ -0,0 +1,101 @@
|
||||
# index.hbs
|
||||
|
||||
`index.hbs` is the handlebars template that is used to render the book. The
|
||||
markdown files are processed to html and then injected in that template.
|
||||
|
||||
If you want to change the layout or style of your book, chances are that you
|
||||
will have to modify this template a little bit. Here is what you need to know.
|
||||
|
||||
## Data
|
||||
|
||||
A lot of data is exposed to the handlebars template with the "context". In the
|
||||
handlebars template you can access this information by using
|
||||
|
||||
```handlebars
|
||||
{{name_of_property}}
|
||||
```
|
||||
|
||||
Here is a list of the properties that are exposed:
|
||||
|
||||
- ***language*** Language of the book in the form `en`, as specified in `book.toml` (if not specified, defaults to `en`). To use in <code
|
||||
class="language-html">\<html lang="{{ language }}"></code> for example.
|
||||
- ***title*** Title used for the current page. This is identical to `{{ chapter_title }} - {{ book_title }}` unless `book_title` is not set in which case it just defaults to the `chapter_title`.
|
||||
- ***book_title*** Title of the book, as specified in `book.toml`
|
||||
- ***chapter_title*** Title of the current chapter, as listed in `SUMMARY.md`
|
||||
|
||||
- ***path*** Relative path to the original markdown file from the source
|
||||
directory
|
||||
- ***content*** This is the rendered markdown.
|
||||
- ***path_to_root*** This is a path containing exclusively `../`'s that points
|
||||
to the root of the book from the current file. Since the original directory
|
||||
structure is maintained, it is useful to prepend relative links with this
|
||||
`path_to_root`.
|
||||
|
||||
- ***chapters*** Is an array of dictionaries of the form
|
||||
```json
|
||||
{"section": "1.2.1", "name": "name of this chapter", "path": "dir/markdown.md"}
|
||||
```
|
||||
containing all the chapters of the book. It is used for example to construct
|
||||
the table of contents (sidebar).
|
||||
|
||||
## Handlebars Helpers
|
||||
|
||||
In addition to the properties you can access, there are some handlebars helpers
|
||||
at your disposal.
|
||||
|
||||
### 1. toc
|
||||
|
||||
The toc helper is used like this
|
||||
|
||||
```handlebars
|
||||
{{#toc}}{{/toc}}
|
||||
```
|
||||
|
||||
and outputs something that looks like this, depending on the structure of your
|
||||
book
|
||||
|
||||
```html
|
||||
<ul class="chapter">
|
||||
<li><a href="link/to/file.html">Some chapter</a></li>
|
||||
<li>
|
||||
<ul class="section">
|
||||
<li><a href="link/to/other_file.html">Some other Chapter</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
```
|
||||
|
||||
If you would like to make a toc with another structure, you have access to the
|
||||
chapters property containing all the data. The only limitation at the moment
|
||||
is that you would have to do it with JavaScript instead of with a handlebars
|
||||
helper.
|
||||
|
||||
```html
|
||||
<script>
|
||||
var chapters = {{chapters}};
|
||||
// Processing here
|
||||
</script>
|
||||
```
|
||||
|
||||
### 2. previous / next
|
||||
|
||||
The previous and next helpers expose a `link` and `name` property to the
|
||||
previous and next chapters.
|
||||
|
||||
They are used like this
|
||||
|
||||
```handlebars
|
||||
{{#previous}}
|
||||
<a href="{{link}}" class="nav-chapters previous">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
{{/previous}}
|
||||
```
|
||||
|
||||
The inner html will only be rendered if the previous / next chapter exists.
|
||||
Of course the inner html can be changed to your liking.
|
||||
|
||||
------
|
||||
|
||||
*If you would like other properties or helpers exposed, please [create a new
|
||||
issue](https://github.com/rust-lang/mdBook/issues)*
|
||||
121
guide/src/format/theme/syntax-highlighting.md
Normal file
121
guide/src/format/theme/syntax-highlighting.md
Normal file
@@ -0,0 +1,121 @@
|
||||
# Syntax Highlighting
|
||||
|
||||
mdBook uses [Highlight.js](https://highlightjs.org) with a custom theme
|
||||
for syntax highlighting.
|
||||
|
||||
Automatic language detection has been turned off, so you will probably want to
|
||||
specify the programming language you use like this:
|
||||
|
||||
~~~markdown
|
||||
```rust
|
||||
fn main() {
|
||||
// Some code
|
||||
}
|
||||
```
|
||||
~~~
|
||||
|
||||
## Supported languages
|
||||
|
||||
These languages are supported by default, but you can add more by supplying
|
||||
your own `highlight.js` file:
|
||||
|
||||
- apache
|
||||
- armasm
|
||||
- bash
|
||||
- c
|
||||
- coffeescript
|
||||
- cpp
|
||||
- csharp
|
||||
- css
|
||||
- d
|
||||
- diff
|
||||
- go
|
||||
- handlebars
|
||||
- haskell
|
||||
- http
|
||||
- ini
|
||||
- java
|
||||
- javascript
|
||||
- json
|
||||
- julia
|
||||
- kotlin
|
||||
- less
|
||||
- lua
|
||||
- makefile
|
||||
- markdown
|
||||
- nginx
|
||||
- objectivec
|
||||
- perl
|
||||
- php
|
||||
- plaintext
|
||||
- properties
|
||||
- python
|
||||
- r
|
||||
- ruby
|
||||
- rust
|
||||
- scala
|
||||
- scss
|
||||
- shell
|
||||
- sql
|
||||
- swift
|
||||
- typescript
|
||||
- x86asm
|
||||
- xml
|
||||
- yaml
|
||||
|
||||
## Custom theme
|
||||
Like the rest of the theme, the files used for syntax highlighting can be
|
||||
overridden with your own.
|
||||
|
||||
- ***highlight.js*** normally you shouldn't have to overwrite this file, unless
|
||||
you want to use a more recent version.
|
||||
- ***highlight.css*** theme used by highlight.js for syntax highlighting.
|
||||
|
||||
If you want to use another theme for `highlight.js` download it from their
|
||||
website, or make it yourself, rename it to `highlight.css` and put it in
|
||||
the `theme` folder of your book.
|
||||
|
||||
Now your theme will be used instead of the default theme.
|
||||
|
||||
## Hiding code lines
|
||||
|
||||
There is a feature in mdBook that lets you hide code lines by prepending them
|
||||
with a `#`.
|
||||
|
||||
|
||||
```bash
|
||||
# fn main() {
|
||||
let x = 5;
|
||||
let y = 6;
|
||||
|
||||
println!("{}", x + y);
|
||||
# }
|
||||
```
|
||||
|
||||
Will render as
|
||||
|
||||
```rust
|
||||
# fn main() {
|
||||
let x = 5;
|
||||
let y = 7;
|
||||
|
||||
println!("{}", x + y);
|
||||
# }
|
||||
```
|
||||
|
||||
**At the moment, this only works for code examples that are annotated with
|
||||
`rust`. Because it would collide with semantics of some programming languages.
|
||||
In the future, we want to make this configurable through the `book.toml` so that
|
||||
everyone can benefit from it.**
|
||||
|
||||
|
||||
## Improve default theme
|
||||
|
||||
If you think the default theme doesn't look quite right for a specific language,
|
||||
or could be improved, feel free to [submit a new
|
||||
issue](https://github.com/rust-lang/mdBook/issues) explaining what you
|
||||
have in mind and I will take a look at it.
|
||||
|
||||
You could also create a pull-request with the proposed improvements.
|
||||
|
||||
Overall the theme should be light and sober, without too many flashy colors.
|
||||
@@ -1,8 +1,7 @@
|
||||
# Contributors
|
||||
|
||||
Here is a list of the contributors who have helped improving mdBook. Big shout-out to them!
|
||||
|
||||
If you have contributed to mdBook and I forgot to add you, don't hesitate to add yourself to the list. If you are in the list, feel free to add your real name & contact information if you wish.
|
||||
Here is a list of the contributors who have helped improving mdBook. Big
|
||||
shout-out to them!
|
||||
|
||||
- [mdinger](https://github.com/mdinger)
|
||||
- Kevin ([kbknapp](https://github.com/kbknapp))
|
||||
@@ -12,5 +11,12 @@ If you have contributed to mdBook and I forgot to add you, don't hesitate to add
|
||||
- [funnkill](https://github.com/funkill)
|
||||
- Fu Gangqiang ([FuGangqiang](https://github.com/FuGangqiang))
|
||||
- [Michael-F-Bryan](https://github.com/Michael-F-Bryan)
|
||||
- [Chris Spiegel](https://github.com/cspiegel)
|
||||
- Chris Spiegel ([cspiegel](https://github.com/cspiegel))
|
||||
- [projektir](https://github.com/projektir)
|
||||
- [Phaiax](https://github.com/Phaiax)
|
||||
- Matt Ickstadt ([mattico](https://github.com/mattico))
|
||||
- Weihang Lo ([@weihanglo](https://github.com/weihanglo))
|
||||
- Avision Ho ([@avisionh](https://github.com/avisionh))
|
||||
- Vivek Akupatni ([@apatniv](https://github.com/apatniv))
|
||||
|
||||
If you feel you're missing from this list, feel free to add yourself in a PR.
|
||||
3
release.toml
Normal file
3
release.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
sign-commit = true
|
||||
push-remote = "origin"
|
||||
tag-prefix = "v"
|
||||
@@ -1,7 +0,0 @@
|
||||
array_layout = "Visual"
|
||||
chain_indent = "Visual"
|
||||
fn_args_layout = "Visual"
|
||||
fn_call_style = "Visual"
|
||||
format_strings = true
|
||||
generics_indent = "Visual"
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
use clap::{App, ArgMatches, SubCommand};
|
||||
use mdbook::MDBook;
|
||||
use mdbook::errors::Result;
|
||||
use get_book_dir;
|
||||
|
||||
// Create clap subcommand arguments
|
||||
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
|
||||
SubCommand::with_name("init")
|
||||
.about("Create boilerplate structure and files in the directory")
|
||||
// the {n} denotes a newline which will properly aligned in all help messages
|
||||
.arg_from_usage("[dir] 'A directory for your book{n}(Defaults to Current Directory \
|
||||
when omitted)'")
|
||||
.arg_from_usage("--theme 'Copies the default theme into your source folder'")
|
||||
.arg_from_usage("--force 'skip confirmation prompts'")
|
||||
}
|
||||
|
||||
// Init command implementation
|
||||
pub fn execute(args: &ArgMatches) -> Result<()> {
|
||||
let book_dir = get_book_dir(args);
|
||||
let mut builder = MDBook::init(&book_dir);
|
||||
|
||||
// If flag `--theme` is present, copy theme to src
|
||||
if args.is_present("theme") {
|
||||
// Skip this if `--force` is present
|
||||
if !args.is_present("force") {
|
||||
// Print warning
|
||||
println!();
|
||||
println!(
|
||||
"Copying the default theme to {}",
|
||||
builder.config().book.src.display()
|
||||
);
|
||||
println!("This could potentially overwrite files already present in that directory.");
|
||||
print!("\nAre you sure you want to continue? (y/n) ");
|
||||
|
||||
// Read answer from user and exit if it's not 'yes'
|
||||
if confirm() {
|
||||
builder.copy_theme(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("\nDo you want a .gitignore to be created? (y/n)");
|
||||
|
||||
if confirm() {
|
||||
builder.create_gitignore(true);
|
||||
}
|
||||
|
||||
builder.build()?;
|
||||
println!("\nAll done, no errors...");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Simple function that user comfirmation
|
||||
fn confirm() -> bool {
|
||||
io::stdout().flush().unwrap();
|
||||
let mut s = String::new();
|
||||
io::stdin().read_line(&mut s).ok();
|
||||
match &*s.trim() {
|
||||
"Y" | "y" | "yes" | "Yes" => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
#[macro_use]
|
||||
extern crate clap;
|
||||
extern crate chrono;
|
||||
extern crate env_logger;
|
||||
extern crate error_chain;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate mdbook;
|
||||
extern crate open;
|
||||
|
||||
use std::env;
|
||||
use std::ffi::OsStr;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::io::Write;
|
||||
use clap::{App, AppSettings, ArgMatches};
|
||||
use chrono::Local;
|
||||
use log::LevelFilter;
|
||||
use env_logger::Builder;
|
||||
use mdbook::utils;
|
||||
|
||||
pub mod build;
|
||||
pub mod init;
|
||||
pub mod test;
|
||||
#[cfg(feature = "serve")]
|
||||
pub mod serve;
|
||||
#[cfg(feature = "watch")]
|
||||
pub mod watch;
|
||||
|
||||
const NAME: &'static str = "mdbook";
|
||||
|
||||
fn main() {
|
||||
init_logger();
|
||||
|
||||
// Create a list of valid arguments and sub-commands
|
||||
let app = App::new(NAME)
|
||||
.about("Create a book in form of a static website from markdown files")
|
||||
.author("Mathieu David <mathieudavid@mathieudavid.org>")
|
||||
// Get the version from our Cargo.toml using clap's crate_version!() macro
|
||||
.version(concat!("v",crate_version!()))
|
||||
.setting(AppSettings::SubcommandRequired)
|
||||
.after_help("For more information about a specific command, \
|
||||
try `mdbook <command> --help`\n\
|
||||
Source code for mdbook available \
|
||||
at: https://github.com/rust-lang-nursery/mdBook")
|
||||
.subcommand(init::make_subcommand())
|
||||
.subcommand(build::make_subcommand())
|
||||
.subcommand(test::make_subcommand());
|
||||
|
||||
#[cfg(feature = "watch")]
|
||||
let app = app.subcommand(watch::make_subcommand());
|
||||
#[cfg(feature = "serve")]
|
||||
let app = app.subcommand(serve::make_subcommand());
|
||||
|
||||
// Check which subcomamnd the user ran...
|
||||
let res = match app.get_matches().subcommand() {
|
||||
("init", Some(sub_matches)) => init::execute(sub_matches),
|
||||
("build", Some(sub_matches)) => build::execute(sub_matches),
|
||||
#[cfg(feature = "watch")]
|
||||
("watch", Some(sub_matches)) => watch::execute(sub_matches),
|
||||
#[cfg(feature = "serve")]
|
||||
("serve", Some(sub_matches)) => serve::execute(sub_matches),
|
||||
("test", Some(sub_matches)) => test::execute(sub_matches),
|
||||
(_, _) => unreachable!(),
|
||||
};
|
||||
|
||||
if let Err(e) = res {
|
||||
utils::log_backtrace(&e);
|
||||
|
||||
::std::process::exit(101);
|
||||
}
|
||||
}
|
||||
|
||||
fn init_logger() {
|
||||
let mut builder = Builder::new();
|
||||
|
||||
builder.format(|formatter, record| {
|
||||
writeln!(formatter, "{} [{}] ({}): {}",
|
||||
Local::now().format("%Y-%m-%d %H:%M:%S"),
|
||||
record.level(),
|
||||
record.target(),
|
||||
record.args())
|
||||
});
|
||||
|
||||
if let Ok(var) = env::var("RUST_LOG") {
|
||||
builder.parse(&var);
|
||||
} else {
|
||||
// if no RUST_LOG provided, default to logging at the Info level
|
||||
builder.filter(None, LevelFilter::Info);
|
||||
}
|
||||
|
||||
builder.init();
|
||||
}
|
||||
|
||||
fn get_book_dir(args: &ArgMatches) -> PathBuf {
|
||||
if let Some(dir) = args.value_of("dir") {
|
||||
// Check if path is relative from current dir, or absolute...
|
||||
let p = Path::new(dir);
|
||||
if p.is_relative() {
|
||||
env::current_dir().unwrap().join(dir)
|
||||
} else {
|
||||
p.to_path_buf()
|
||||
}
|
||||
} else {
|
||||
env::current_dir().expect("Unable to determine the current directory")
|
||||
}
|
||||
}
|
||||
|
||||
fn open<P: AsRef<OsStr>>(path: P) {
|
||||
if let Err(e) = open::that(path) {
|
||||
error!("Error opening web browser: {}", e);
|
||||
}
|
||||
}
|
||||
117
src/bin/serve.rs
117
src/bin/serve.rs
@@ -1,117 +0,0 @@
|
||||
extern crate iron;
|
||||
extern crate staticfile;
|
||||
extern crate ws;
|
||||
|
||||
use std;
|
||||
use self::iron::{status, AfterMiddleware, Chain, Iron, IronError, IronResult, Request, Response,
|
||||
Set};
|
||||
use clap::{App, ArgMatches, SubCommand};
|
||||
use mdbook::MDBook;
|
||||
use mdbook::utils;
|
||||
use mdbook::errors::*;
|
||||
use {get_book_dir, open};
|
||||
#[cfg(feature = "watch")]
|
||||
use watch;
|
||||
|
||||
struct ErrorRecover;
|
||||
|
||||
// Create clap subcommand arguments
|
||||
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
|
||||
SubCommand::with_name("serve")
|
||||
.about("Serve the book at http://localhost:3000. Rebuild and reload on change.")
|
||||
.arg_from_usage(
|
||||
"[dir] 'A directory for your book{n}(Defaults to Current Directory when omitted)'",
|
||||
)
|
||||
.arg_from_usage("-p, --port=[port] 'Use another port{n}(Defaults to 3000)'")
|
||||
.arg_from_usage(
|
||||
"-w, --websocket-port=[ws-port] 'Use another port for the websocket connection \
|
||||
(livereload){n}(Defaults to 3001)'",
|
||||
)
|
||||
.arg_from_usage(
|
||||
"-i, --interface=[interface] 'Interface to listen on{n}(Defaults to localhost)'",
|
||||
)
|
||||
.arg_from_usage(
|
||||
"-a, --address=[address] 'Address that the browser can reach the websocket server \
|
||||
from{n}(Defaults to the interface address)'",
|
||||
)
|
||||
.arg_from_usage("-o, --open 'Open the book server in a web browser'")
|
||||
}
|
||||
|
||||
// Watch command implementation
|
||||
pub fn execute(args: &ArgMatches) -> Result<()> {
|
||||
let book_dir = get_book_dir(args);
|
||||
let mut book = MDBook::load(&book_dir)?;
|
||||
|
||||
let port = args.value_of("port").unwrap_or("3000");
|
||||
let ws_port = args.value_of("websocket-port").unwrap_or("3001");
|
||||
let interface = args.value_of("interface").unwrap_or("localhost");
|
||||
let public_address = args.value_of("address").unwrap_or(interface);
|
||||
let open_browser = args.is_present("open");
|
||||
|
||||
let address = format!("{}:{}", interface, port);
|
||||
let ws_address = format!("{}:{}", interface, ws_port);
|
||||
|
||||
let livereload_url = format!("ws://{}:{}", public_address, ws_port);
|
||||
book.config
|
||||
.set("output.html.livereload-url", &livereload_url)?;
|
||||
|
||||
book.build()?;
|
||||
|
||||
let mut chain = Chain::new(staticfile::Static::new(book.build_dir_for("html")));
|
||||
chain.link_after(ErrorRecover);
|
||||
let _iron = Iron::new(chain)
|
||||
.http(&*address)
|
||||
.chain_err(|| "Unable to launch the server")?;
|
||||
|
||||
let ws_server =
|
||||
ws::WebSocket::new(|_| |_| Ok(())).chain_err(|| "Unable to start the websocket")?;
|
||||
|
||||
let broadcaster = ws_server.broadcaster();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
ws_server.listen(&*ws_address).unwrap();
|
||||
});
|
||||
|
||||
let serving_url = format!("http://{}", address);
|
||||
info!("Serving on: {}", serving_url);
|
||||
|
||||
if open_browser {
|
||||
open(serving_url);
|
||||
}
|
||||
|
||||
#[cfg(feature = "watch")]
|
||||
watch::trigger_on_change(&mut book, move |path, book_dir| {
|
||||
info!("File changed: {:?}", path);
|
||||
info!("Building book...");
|
||||
|
||||
// FIXME: This area is really ugly because we need to re-set livereload :(
|
||||
|
||||
let livereload_url = livereload_url.clone();
|
||||
|
||||
let result = MDBook::load(&book_dir)
|
||||
.and_then(move |mut b| {
|
||||
b.config.set("output.html.livereload-url", &livereload_url)?;
|
||||
Ok(b)
|
||||
})
|
||||
.and_then(|b| b.build());
|
||||
|
||||
if let Err(e) = result {
|
||||
error!("Unable to load the book");
|
||||
utils::log_backtrace(&e);
|
||||
} else {
|
||||
let _ = broadcaster.send("reload");
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl AfterMiddleware for ErrorRecover {
|
||||
fn catch(&self, _: &mut Request, err: IronError) -> IronResult<Response> {
|
||||
match err.response.status {
|
||||
// each error will result in 404 response
|
||||
Some(_) => Ok(err.response.set(status::NotFound)),
|
||||
_ => Err(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
use clap::{App, ArgMatches, SubCommand};
|
||||
use mdbook::MDBook;
|
||||
use mdbook::errors::Result;
|
||||
use get_book_dir;
|
||||
|
||||
// Create clap subcommand arguments
|
||||
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
|
||||
SubCommand::with_name("test")
|
||||
.about("Test that code samples compile")
|
||||
.arg_from_usage(
|
||||
"-L, --library-path [DIR]... 'directory to add to crate search path'",
|
||||
)
|
||||
}
|
||||
|
||||
// test command implementation
|
||||
pub fn execute(args: &ArgMatches) -> Result<()> {
|
||||
let library_paths: Vec<&str> = args.values_of("library-path")
|
||||
.map(|v| v.collect())
|
||||
.unwrap_or_default();
|
||||
let book_dir = get_book_dir(args);
|
||||
let mut book = MDBook::load(&book_dir)?;
|
||||
|
||||
book.test(library_paths)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
extern crate notify;
|
||||
|
||||
use std::path::Path;
|
||||
use self::notify::Watcher;
|
||||
use std::time::Duration;
|
||||
use std::sync::mpsc::channel;
|
||||
use clap::{App, ArgMatches, SubCommand};
|
||||
use mdbook::MDBook;
|
||||
use mdbook::utils;
|
||||
use mdbook::errors::Result;
|
||||
use {get_book_dir, open};
|
||||
|
||||
// Create clap subcommand arguments
|
||||
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
|
||||
SubCommand::with_name("watch")
|
||||
.about("Watch the files for changes")
|
||||
.arg_from_usage("-o, --open 'Open the compiled book in a web browser'")
|
||||
.arg_from_usage(
|
||||
"[dir] 'A directory for your book{n}(Defaults to Current Directory when omitted)'",
|
||||
)
|
||||
}
|
||||
|
||||
// Watch command implementation
|
||||
pub fn execute(args: &ArgMatches) -> Result<()> {
|
||||
let book_dir = get_book_dir(args);
|
||||
let book = MDBook::load(&book_dir)?;
|
||||
|
||||
if args.is_present("open") {
|
||||
book.build()?;
|
||||
open(book.build_dir_for("html").join("index.html"));
|
||||
}
|
||||
|
||||
trigger_on_change(&book, |path, book_dir| {
|
||||
info!("File changed: {:?}\nBuilding book...\n", path);
|
||||
let result = MDBook::load(&book_dir).and_then(|b| b.build());
|
||||
|
||||
if let Err(e) = result {
|
||||
error!("Unable to build the book");
|
||||
utils::log_backtrace(&e);
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Calls the closure when a book source file is changed, blocking indefinitely.
|
||||
pub fn trigger_on_change<F>(book: &MDBook, closure: F)
|
||||
where
|
||||
F: Fn(&Path, &Path),
|
||||
{
|
||||
use self::notify::RecursiveMode::*;
|
||||
use self::notify::DebouncedEvent::*;
|
||||
|
||||
// Create a channel to receive the events.
|
||||
let (tx, rx) = channel();
|
||||
|
||||
let mut watcher = match notify::watcher(tx, Duration::from_secs(1)) {
|
||||
Ok(w) => w,
|
||||
Err(e) => {
|
||||
error!("Error while trying to watch the files:\n\n\t{:?}", e);
|
||||
::std::process::exit(1)
|
||||
}
|
||||
};
|
||||
|
||||
// Add the source directory to the watcher
|
||||
if let Err(e) = watcher.watch(book.source_dir(), Recursive) {
|
||||
error!("Error while watching {:?}:\n {:?}", book.source_dir(), e);
|
||||
::std::process::exit(1);
|
||||
};
|
||||
|
||||
let _ = watcher.watch(book.theme_dir(), Recursive);
|
||||
|
||||
// Add the book.toml file to the watcher if it exists
|
||||
let _ = watcher.watch(book.root.join("book.toml"), NonRecursive);
|
||||
|
||||
info!("Listening for changes...");
|
||||
|
||||
for event in rx.iter() {
|
||||
debug!("Received filesystem event: {:?}", event);
|
||||
match event {
|
||||
Create(path) | Write(path) | Remove(path) | Rename(_, path) => {
|
||||
closure(&path, &book.root);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
282
src/book/book.rs
282
src/book/book.rs
@@ -1,12 +1,12 @@
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::fs::{self, File};
|
||||
use std::io::{Read, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use super::summary::{parse_summary, Link, SectionNumber, Summary, SummaryItem};
|
||||
use config::BuildConfig;
|
||||
use errors::*;
|
||||
use crate::config::BuildConfig;
|
||||
use crate::errors::*;
|
||||
|
||||
/// Load a book into memory from its `src/` directory.
|
||||
pub fn load_book<P: AsRef<Path>>(src_dir: P, cfg: &BuildConfig) -> Result<Book> {
|
||||
@@ -14,14 +14,15 @@ pub fn load_book<P: AsRef<Path>>(src_dir: P, cfg: &BuildConfig) -> Result<Book>
|
||||
let summary_md = src_dir.join("SUMMARY.md");
|
||||
|
||||
let mut summary_content = String::new();
|
||||
File::open(summary_md)
|
||||
.chain_err(|| "Couldn't open SUMMARY.md")?
|
||||
File::open(&summary_md)
|
||||
.with_context(|| format!("Couldn't open SUMMARY.md in {:?} directory", src_dir))?
|
||||
.read_to_string(&mut summary_content)?;
|
||||
|
||||
let summary = parse_summary(&summary_content).chain_err(|| "Summary parsing failed")?;
|
||||
let summary = parse_summary(&summary_content)
|
||||
.with_context(|| format!("Summary parsing failed for file={:?}", summary_md))?;
|
||||
|
||||
if cfg.create_missing {
|
||||
create_missing(&src_dir, &summary).chain_err(|| "Unable to create missing chapters")?;
|
||||
create_missing(&src_dir, &summary).with_context(|| "Unable to create missing chapters")?;
|
||||
}
|
||||
|
||||
load_book_from_disk(&summary, src_dir)
|
||||
@@ -39,17 +40,21 @@ fn create_missing(src_dir: &Path, summary: &Summary) -> Result<()> {
|
||||
let next = items.pop().expect("already checked");
|
||||
|
||||
if let SummaryItem::Link(ref link) = *next {
|
||||
let filename = src_dir.join(&link.location);
|
||||
if !filename.exists() {
|
||||
if let Some(parent) = filename.parent() {
|
||||
if !parent.exists() {
|
||||
fs::create_dir_all(parent)?;
|
||||
if let Some(ref location) = link.location {
|
||||
let filename = src_dir.join(location);
|
||||
if !filename.exists() {
|
||||
if let Some(parent) = filename.parent() {
|
||||
if !parent.exists() {
|
||||
fs::create_dir_all(parent)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
debug!("Creating missing file {}", filename.display());
|
||||
debug!("Creating missing file {}", filename.display());
|
||||
|
||||
let mut f = File::create(&filename)?;
|
||||
writeln!(f, "# {}", link.name)?;
|
||||
let mut f = File::create(&filename).with_context(|| {
|
||||
format!("Unable to create missing file: {}", filename.display())
|
||||
})?;
|
||||
writeln!(f, "# {}", link.name)?;
|
||||
}
|
||||
}
|
||||
|
||||
items.extend(&link.nested_items);
|
||||
@@ -61,7 +66,7 @@ fn create_missing(src_dir: &Path, summary: &Summary) -> Result<()> {
|
||||
|
||||
/// A dumb tree structure representing a book.
|
||||
///
|
||||
/// For the moment a book is just a collection of `BookItems` which are
|
||||
/// For the moment a book is just a collection of [`BookItems`] which are
|
||||
/// accessible by either iterating (immutably) over the book with [`iter()`], or
|
||||
/// recursively applying a closure to each section to mutate the chapters, using
|
||||
/// [`for_each_mut()`].
|
||||
@@ -71,7 +76,8 @@ fn create_missing(src_dir: &Path, summary: &Summary) -> Result<()> {
|
||||
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Book {
|
||||
/// The sections in this book.
|
||||
sections: Vec<BookItem>,
|
||||
pub sections: Vec<BookItem>,
|
||||
__non_exhaustive: (),
|
||||
}
|
||||
|
||||
impl Book {
|
||||
@@ -81,7 +87,7 @@ impl Book {
|
||||
}
|
||||
|
||||
/// Get a depth-first iterator over the items in the book.
|
||||
pub fn iter(&self) -> BookItems {
|
||||
pub fn iter(&self) -> BookItems<'_> {
|
||||
BookItems {
|
||||
items: self.sections.iter().collect(),
|
||||
}
|
||||
@@ -115,7 +121,7 @@ where
|
||||
I: IntoIterator<Item = &'a mut BookItem>,
|
||||
{
|
||||
for item in items {
|
||||
if let &mut BookItem::Chapter(ref mut ch) = item {
|
||||
if let BookItem::Chapter(ch) = item {
|
||||
for_each_mut(func, &mut ch.sub_items);
|
||||
}
|
||||
|
||||
@@ -130,6 +136,8 @@ pub enum BookItem {
|
||||
Chapter(Chapter),
|
||||
/// A section separator.
|
||||
Separator,
|
||||
/// A part title.
|
||||
PartTitle(String),
|
||||
}
|
||||
|
||||
impl From<Chapter> for BookItem {
|
||||
@@ -151,26 +159,56 @@ pub struct Chapter {
|
||||
/// Nested items.
|
||||
pub sub_items: Vec<BookItem>,
|
||||
/// The chapter's location, relative to the `SUMMARY.md` file.
|
||||
pub path: PathBuf,
|
||||
pub path: Option<PathBuf>,
|
||||
/// The chapter's source file, relative to the `SUMMARY.md` file.
|
||||
pub source_path: Option<PathBuf>,
|
||||
/// An ordered list of the names of each chapter above this one in the hierarchy.
|
||||
pub parent_names: Vec<String>,
|
||||
}
|
||||
|
||||
impl Chapter {
|
||||
/// Create a new chapter with the provided content.
|
||||
pub fn new<P: Into<PathBuf>>(name: &str, content: String, path: P) -> Chapter {
|
||||
pub fn new<P: Into<PathBuf>>(
|
||||
name: &str,
|
||||
content: String,
|
||||
p: P,
|
||||
parent_names: Vec<String>,
|
||||
) -> Chapter {
|
||||
let path: PathBuf = p.into();
|
||||
Chapter {
|
||||
name: name.to_string(),
|
||||
content: content,
|
||||
path: path.into(),
|
||||
content,
|
||||
path: Some(path.clone()),
|
||||
source_path: Some(path),
|
||||
parent_names,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new draft chapter that is not attached to a source markdown file (and thus
|
||||
/// has no content).
|
||||
pub fn new_draft(name: &str, parent_names: Vec<String>) -> Self {
|
||||
Chapter {
|
||||
name: name.to_string(),
|
||||
content: String::new(),
|
||||
path: None,
|
||||
source_path: None,
|
||||
parent_names,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the chapter is a draft chapter, meaning it has no path to a source markdown file.
|
||||
pub fn is_draft_chapter(&self) -> bool {
|
||||
self.path.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
/// Use the provided `Summary` to load a `Book` from disk.
|
||||
///
|
||||
/// You need to pass in the book's source directory because all the links in
|
||||
/// `SUMMARY.md` give the chapter locations relative to it.
|
||||
fn load_book_from_disk<P: AsRef<Path>>(summary: &Summary, src_dir: P) -> Result<Book> {
|
||||
pub(crate) fn load_book_from_disk<P: AsRef<Path>>(summary: &Summary, src_dir: P) -> Result<Book> {
|
||||
debug!("Loading the book from disk");
|
||||
let src_dir = src_dir.as_ref();
|
||||
|
||||
@@ -183,47 +221,76 @@ fn load_book_from_disk<P: AsRef<Path>>(summary: &Summary, src_dir: P) -> Result<
|
||||
let mut chapters = Vec::new();
|
||||
|
||||
for summary_item in summary_items {
|
||||
let chapter = load_summary_item(summary_item, src_dir)?;
|
||||
let chapter = load_summary_item(summary_item, src_dir, Vec::new())?;
|
||||
chapters.push(chapter);
|
||||
}
|
||||
|
||||
Ok(Book { sections: chapters })
|
||||
Ok(Book {
|
||||
sections: chapters,
|
||||
__non_exhaustive: (),
|
||||
})
|
||||
}
|
||||
|
||||
fn load_summary_item<P: AsRef<Path>>(item: &SummaryItem, src_dir: P) -> Result<BookItem> {
|
||||
match *item {
|
||||
fn load_summary_item<P: AsRef<Path> + Clone>(
|
||||
item: &SummaryItem,
|
||||
src_dir: P,
|
||||
parent_names: Vec<String>,
|
||||
) -> Result<BookItem> {
|
||||
match item {
|
||||
SummaryItem::Separator => Ok(BookItem::Separator),
|
||||
SummaryItem::Link(ref link) => load_chapter(link, src_dir).map(|c| BookItem::Chapter(c)),
|
||||
SummaryItem::Link(ref link) => {
|
||||
load_chapter(link, src_dir, parent_names).map(BookItem::Chapter)
|
||||
}
|
||||
SummaryItem::PartTitle(title) => Ok(BookItem::PartTitle(title.clone())),
|
||||
}
|
||||
}
|
||||
|
||||
fn load_chapter<P: AsRef<Path>>(link: &Link, src_dir: P) -> Result<Chapter> {
|
||||
debug!("Loading {} ({})", link.name, link.location.display());
|
||||
fn load_chapter<P: AsRef<Path>>(
|
||||
link: &Link,
|
||||
src_dir: P,
|
||||
parent_names: Vec<String>,
|
||||
) -> Result<Chapter> {
|
||||
let src_dir = src_dir.as_ref();
|
||||
|
||||
let location = if link.location.is_absolute() {
|
||||
link.location.clone()
|
||||
let mut ch = if let Some(ref link_location) = link.location {
|
||||
debug!("Loading {} ({})", link.name, link_location.display());
|
||||
|
||||
let location = if link_location.is_absolute() {
|
||||
link_location.clone()
|
||||
} else {
|
||||
src_dir.join(link_location)
|
||||
};
|
||||
|
||||
let mut f = File::open(&location)
|
||||
.with_context(|| format!("Chapter file not found, {}", link_location.display()))?;
|
||||
|
||||
let mut content = String::new();
|
||||
f.read_to_string(&mut content).with_context(|| {
|
||||
format!("Unable to read \"{}\" ({})", link.name, location.display())
|
||||
})?;
|
||||
|
||||
if content.as_bytes().starts_with(b"\xef\xbb\xbf") {
|
||||
content.replace_range(..3, "");
|
||||
}
|
||||
|
||||
let stripped = location
|
||||
.strip_prefix(&src_dir)
|
||||
.expect("Chapters are always inside a book");
|
||||
|
||||
Chapter::new(&link.name, content, stripped, parent_names.clone())
|
||||
} else {
|
||||
src_dir.join(&link.location)
|
||||
Chapter::new_draft(&link.name, parent_names.clone())
|
||||
};
|
||||
|
||||
let mut f = File::open(&location)
|
||||
.chain_err(|| format!("Chapter file not found, {}", link.location.display()))?;
|
||||
let mut sub_item_parents = parent_names;
|
||||
|
||||
let mut content = String::new();
|
||||
f.read_to_string(&mut content)
|
||||
.chain_err(|| format!("Unable to read \"{}\" ({})", link.name, location.display()))?;
|
||||
|
||||
let stripped = location
|
||||
.strip_prefix(&src_dir)
|
||||
.expect("Chapters are always inside a book");
|
||||
|
||||
let mut ch = Chapter::new(&link.name, content, stripped);
|
||||
ch.number = link.number.clone();
|
||||
|
||||
let sub_items = link.nested_items
|
||||
sub_item_parents.push(link.name.clone());
|
||||
let sub_items = link
|
||||
.nested_items
|
||||
.iter()
|
||||
.map(|i| load_summary_item(i, src_dir))
|
||||
.map(|i| load_summary_item(i, src_dir, sub_item_parents.clone()))
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
|
||||
ch.sub_items = sub_items;
|
||||
@@ -237,8 +304,6 @@ fn load_chapter<P: AsRef<Path>>(link: &Link, src_dir: P) -> Result<Chapter> {
|
||||
///
|
||||
/// This struct shouldn't be created directly, instead prefer the
|
||||
/// [`Book::iter()`] method.
|
||||
///
|
||||
/// [`Book::iter()`]: struct.Book.html#method.iter
|
||||
pub struct BookItems<'a> {
|
||||
items: VecDeque<&'a BookItem>,
|
||||
}
|
||||
@@ -261,7 +326,7 @@ impl<'a> Iterator for BookItems<'a> {
|
||||
}
|
||||
|
||||
impl Display for Chapter {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
if let Some(ref section_number) = self.number {
|
||||
write!(f, "{} ", section_number)?;
|
||||
}
|
||||
@@ -273,10 +338,10 @@ impl Display for Chapter {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use tempdir::TempDir;
|
||||
use std::io::Write;
|
||||
use tempfile::{Builder as TempFileBuilder, TempDir};
|
||||
|
||||
const DUMMY_SRC: &'static str = "
|
||||
const DUMMY_SRC: &str = "
|
||||
# Dummy Chapter
|
||||
|
||||
this is some dummy text.
|
||||
@@ -287,12 +352,12 @@ And here is some \
|
||||
|
||||
/// Create a dummy `Link` in a temporary directory.
|
||||
fn dummy_link() -> (Link, TempDir) {
|
||||
let temp = TempDir::new("book").unwrap();
|
||||
let temp = TempFileBuilder::new().prefix("book").tempdir().unwrap();
|
||||
|
||||
let chapter_path = temp.path().join("chapter_1.md");
|
||||
File::create(&chapter_path)
|
||||
.unwrap()
|
||||
.write(DUMMY_SRC.as_bytes())
|
||||
.write_all(DUMMY_SRC.as_bytes())
|
||||
.unwrap();
|
||||
|
||||
let link = Link::new("Chapter 1", chapter_path);
|
||||
@@ -308,7 +373,7 @@ And here is some \
|
||||
|
||||
File::create(&second_path)
|
||||
.unwrap()
|
||||
.write_all("Hello World!".as_bytes())
|
||||
.write_all(b"Hello World!")
|
||||
.unwrap();
|
||||
|
||||
let mut second = Link::new("Nested Chapter 1", &second_path);
|
||||
@@ -324,9 +389,37 @@ And here is some \
|
||||
#[test]
|
||||
fn load_a_single_chapter_from_disk() {
|
||||
let (link, temp_dir) = dummy_link();
|
||||
let should_be = Chapter::new("Chapter 1", DUMMY_SRC.to_string(), "chapter_1.md");
|
||||
let should_be = Chapter::new(
|
||||
"Chapter 1",
|
||||
DUMMY_SRC.to_string(),
|
||||
"chapter_1.md",
|
||||
Vec::new(),
|
||||
);
|
||||
|
||||
let got = load_chapter(&link, temp_dir.path()).unwrap();
|
||||
let got = load_chapter(&link, temp_dir.path(), Vec::new()).unwrap();
|
||||
assert_eq!(got, should_be);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_a_single_chapter_with_utf8_bom_from_disk() {
|
||||
let temp_dir = TempFileBuilder::new().prefix("book").tempdir().unwrap();
|
||||
|
||||
let chapter_path = temp_dir.path().join("chapter_1.md");
|
||||
File::create(&chapter_path)
|
||||
.unwrap()
|
||||
.write_all(("\u{feff}".to_owned() + DUMMY_SRC).as_bytes())
|
||||
.unwrap();
|
||||
|
||||
let link = Link::new("Chapter 1", chapter_path);
|
||||
|
||||
let should_be = Chapter::new(
|
||||
"Chapter 1",
|
||||
DUMMY_SRC.to_string(),
|
||||
"chapter_1.md",
|
||||
Vec::new(),
|
||||
);
|
||||
|
||||
let got = load_chapter(&link, temp_dir.path(), Vec::new()).unwrap();
|
||||
assert_eq!(got, should_be);
|
||||
}
|
||||
|
||||
@@ -334,7 +427,7 @@ And here is some \
|
||||
fn cant_load_a_nonexistent_chapter() {
|
||||
let link = Link::new("Chapter 1", "/foo/bar/baz.md");
|
||||
|
||||
let got = load_chapter(&link, "");
|
||||
let got = load_chapter(&link, "", Vec::new());
|
||||
assert!(got.is_err());
|
||||
}
|
||||
|
||||
@@ -346,14 +439,18 @@ And here is some \
|
||||
name: String::from("Nested Chapter 1"),
|
||||
content: String::from("Hello World!"),
|
||||
number: Some(SectionNumber(vec![1, 2])),
|
||||
path: PathBuf::from("second.md"),
|
||||
path: Some(PathBuf::from("second.md")),
|
||||
source_path: Some(PathBuf::from("second.md")),
|
||||
parent_names: vec![String::from("Chapter 1")],
|
||||
sub_items: Vec::new(),
|
||||
};
|
||||
let should_be = BookItem::Chapter(Chapter {
|
||||
name: String::from("Chapter 1"),
|
||||
content: String::from(DUMMY_SRC),
|
||||
number: None,
|
||||
path: PathBuf::from("chapter_1.md"),
|
||||
path: Some(PathBuf::from("chapter_1.md")),
|
||||
source_path: Some(PathBuf::from("chapter_1.md")),
|
||||
parent_names: Vec::new(),
|
||||
sub_items: vec![
|
||||
BookItem::Chapter(nested.clone()),
|
||||
BookItem::Separator,
|
||||
@@ -361,7 +458,7 @@ And here is some \
|
||||
],
|
||||
});
|
||||
|
||||
let got = load_summary_item(&SummaryItem::Link(root), temp.path()).unwrap();
|
||||
let got = load_summary_item(&SummaryItem::Link(root), temp.path(), Vec::new()).unwrap();
|
||||
assert_eq!(got, should_be);
|
||||
}
|
||||
|
||||
@@ -373,14 +470,14 @@ And here is some \
|
||||
..Default::default()
|
||||
};
|
||||
let should_be = Book {
|
||||
sections: vec![
|
||||
BookItem::Chapter(Chapter {
|
||||
name: String::from("Chapter 1"),
|
||||
content: String::from(DUMMY_SRC),
|
||||
path: PathBuf::from("chapter_1.md"),
|
||||
..Default::default()
|
||||
}),
|
||||
],
|
||||
sections: vec![BookItem::Chapter(Chapter {
|
||||
name: String::from("Chapter 1"),
|
||||
content: String::from(DUMMY_SRC),
|
||||
path: Some(PathBuf::from("chapter_1.md")),
|
||||
source_path: Some(PathBuf::from("chapter_1.md")),
|
||||
..Default::default()
|
||||
})],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let got = load_book_from_disk(&summary, temp.path()).unwrap();
|
||||
@@ -399,6 +496,7 @@ And here is some \
|
||||
}),
|
||||
BookItem::Separator,
|
||||
],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let should_be: Vec<_> = book.sections.iter().collect();
|
||||
@@ -416,23 +514,28 @@ And here is some \
|
||||
name: String::from("Chapter 1"),
|
||||
content: String::from(DUMMY_SRC),
|
||||
number: None,
|
||||
path: PathBuf::from("Chapter_1/index.md"),
|
||||
path: Some(PathBuf::from("Chapter_1/index.md")),
|
||||
source_path: Some(PathBuf::from("Chapter_1/index.md")),
|
||||
parent_names: Vec::new(),
|
||||
sub_items: vec![
|
||||
BookItem::Chapter(Chapter::new(
|
||||
"Hello World",
|
||||
String::new(),
|
||||
"Chapter_1/hello.md",
|
||||
Vec::new(),
|
||||
)),
|
||||
BookItem::Separator,
|
||||
BookItem::Chapter(Chapter::new(
|
||||
"Goodbye World",
|
||||
String::new(),
|
||||
"Chapter_1/goodbye.md",
|
||||
Vec::new(),
|
||||
)),
|
||||
],
|
||||
}),
|
||||
BookItem::Separator,
|
||||
],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let got: Vec<_> = book.iter().collect();
|
||||
@@ -440,7 +543,8 @@ And here is some \
|
||||
assert_eq!(got.len(), 5);
|
||||
|
||||
// checking the chapter names are in the order should be sufficient here...
|
||||
let chapter_names: Vec<String> = got.into_iter()
|
||||
let chapter_names: Vec<String> = got
|
||||
.into_iter()
|
||||
.filter_map(|i| match *i {
|
||||
BookItem::Chapter(ref ch) => Some(ch.name.clone()),
|
||||
_ => None,
|
||||
@@ -463,23 +567,28 @@ And here is some \
|
||||
name: String::from("Chapter 1"),
|
||||
content: String::from(DUMMY_SRC),
|
||||
number: None,
|
||||
path: PathBuf::from("Chapter_1/index.md"),
|
||||
path: Some(PathBuf::from("Chapter_1/index.md")),
|
||||
source_path: Some(PathBuf::from("Chapter_1/index.md")),
|
||||
parent_names: Vec::new(),
|
||||
sub_items: vec![
|
||||
BookItem::Chapter(Chapter::new(
|
||||
"Hello World",
|
||||
String::new(),
|
||||
"Chapter_1/hello.md",
|
||||
Vec::new(),
|
||||
)),
|
||||
BookItem::Separator,
|
||||
BookItem::Chapter(Chapter::new(
|
||||
"Goodbye World",
|
||||
String::new(),
|
||||
"Chapter_1/goodbye.md",
|
||||
Vec::new(),
|
||||
)),
|
||||
],
|
||||
}),
|
||||
BookItem::Separator,
|
||||
],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let num_items = book.iter().count();
|
||||
@@ -494,13 +603,12 @@ And here is some \
|
||||
fn cant_load_chapters_with_an_empty_path() {
|
||||
let (_, temp) = dummy_link();
|
||||
let summary = Summary {
|
||||
numbered_chapters: vec![
|
||||
SummaryItem::Link(Link {
|
||||
name: String::from("Empty"),
|
||||
location: PathBuf::from(""),
|
||||
..Default::default()
|
||||
}),
|
||||
],
|
||||
numbered_chapters: vec![SummaryItem::Link(Link {
|
||||
name: String::from("Empty"),
|
||||
location: Some(PathBuf::from("")),
|
||||
..Default::default()
|
||||
})],
|
||||
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
@@ -515,13 +623,11 @@ And here is some \
|
||||
fs::create_dir(&dir).unwrap();
|
||||
|
||||
let summary = Summary {
|
||||
numbered_chapters: vec![
|
||||
SummaryItem::Link(Link {
|
||||
name: String::from("nested"),
|
||||
location: dir,
|
||||
..Default::default()
|
||||
}),
|
||||
],
|
||||
numbered_chapters: vec![SummaryItem::Link(Link {
|
||||
name: String::from("nested"),
|
||||
location: Some(dir),
|
||||
..Default::default()
|
||||
})],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
use std::fs::{self, File};
|
||||
use std::path::PathBuf;
|
||||
use std::io::Write;
|
||||
use toml;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use config::Config;
|
||||
use super::MDBook;
|
||||
use theme;
|
||||
use errors::*;
|
||||
use crate::config::Config;
|
||||
use crate::errors::*;
|
||||
use crate::theme;
|
||||
|
||||
/// A helper for setting up a new book and its directory structure.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
@@ -29,7 +28,7 @@ impl BookBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the `Config` to be used.
|
||||
/// Set the [`Config`] to be used.
|
||||
pub fn with_config(&mut self, cfg: Config) -> &mut BookBuilder {
|
||||
self.config = cfg;
|
||||
self
|
||||
@@ -65,19 +64,19 @@ impl BookBuilder {
|
||||
info!("Creating a new book with stub content");
|
||||
|
||||
self.create_directory_structure()
|
||||
.chain_err(|| "Unable to create directory structure")?;
|
||||
.with_context(|| "Unable to create directory structure")?;
|
||||
|
||||
self.create_stub_files()
|
||||
.chain_err(|| "Unable to create stub files")?;
|
||||
.with_context(|| "Unable to create stub files")?;
|
||||
|
||||
if self.create_gitignore {
|
||||
self.build_gitignore()
|
||||
.chain_err(|| "Unable to create .gitignore")?;
|
||||
.with_context(|| "Unable to create .gitignore")?;
|
||||
}
|
||||
|
||||
if self.copy_theme {
|
||||
self.copy_across_theme()
|
||||
.chain_err(|| "Unable to copy across the theme")?;
|
||||
.with_context(|| "Unable to copy across the theme")?;
|
||||
}
|
||||
|
||||
self.write_book_toml()?;
|
||||
@@ -98,23 +97,20 @@ impl BookBuilder {
|
||||
fn write_book_toml(&self) -> Result<()> {
|
||||
debug!("Writing book.toml");
|
||||
let book_toml = self.root.join("book.toml");
|
||||
let cfg = toml::to_vec(&self.config).chain_err(|| "Unable to serialize the config")?;
|
||||
let cfg = toml::to_vec(&self.config).with_context(|| "Unable to serialize the config")?;
|
||||
|
||||
File::create(book_toml)
|
||||
.chain_err(|| "Couldn't create book.toml")?
|
||||
.with_context(|| "Couldn't create book.toml")?
|
||||
.write_all(&cfg)
|
||||
.chain_err(|| "Unable to write config to book.toml")?;
|
||||
.with_context(|| "Unable to write config to book.toml")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn copy_across_theme(&self) -> Result<()> {
|
||||
debug!("Copying theme");
|
||||
|
||||
let themedir = self.config
|
||||
.html_config()
|
||||
.and_then(|html| html.theme)
|
||||
.unwrap_or_else(|| self.config.book.src.join("theme"));
|
||||
let themedir = self.root.join(themedir);
|
||||
let html_config = self.config.html_config().unwrap_or_default();
|
||||
let themedir = html_config.theme_dir(&self.root);
|
||||
|
||||
if !themedir.exists() {
|
||||
debug!(
|
||||
@@ -127,11 +123,30 @@ impl BookBuilder {
|
||||
let mut index = File::create(themedir.join("index.hbs"))?;
|
||||
index.write_all(theme::INDEX)?;
|
||||
|
||||
let mut css = File::create(themedir.join("book.css"))?;
|
||||
css.write_all(theme::CSS)?;
|
||||
let cssdir = themedir.join("css");
|
||||
if !cssdir.exists() {
|
||||
fs::create_dir(&cssdir)?;
|
||||
}
|
||||
|
||||
let mut general_css = File::create(cssdir.join("general.css"))?;
|
||||
general_css.write_all(theme::GENERAL_CSS)?;
|
||||
|
||||
let mut chrome_css = File::create(cssdir.join("chrome.css"))?;
|
||||
chrome_css.write_all(theme::CHROME_CSS)?;
|
||||
|
||||
if html_config.print.enable {
|
||||
let mut print_css = File::create(cssdir.join("print.css"))?;
|
||||
print_css.write_all(theme::PRINT_CSS)?;
|
||||
}
|
||||
|
||||
let mut variables_css = File::create(cssdir.join("variables.css"))?;
|
||||
variables_css.write_all(theme::VARIABLES_CSS)?;
|
||||
|
||||
let mut favicon = File::create(themedir.join("favicon.png"))?;
|
||||
favicon.write_all(theme::FAVICON)?;
|
||||
favicon.write_all(theme::FAVICON_PNG)?;
|
||||
|
||||
let mut favicon = File::create(themedir.join("favicon.svg"))?;
|
||||
favicon.write_all(theme::FAVICON_SVG)?;
|
||||
|
||||
let mut js = File::create(themedir.join("book.js"))?;
|
||||
js.write_all(theme::JS)?;
|
||||
@@ -160,15 +175,20 @@ impl BookBuilder {
|
||||
let src_dir = self.root.join(&self.config.book.src);
|
||||
|
||||
let summary = src_dir.join("SUMMARY.md");
|
||||
let mut f = File::create(&summary).chain_err(|| "Unable to create SUMMARY.md")?;
|
||||
writeln!(f, "# Summary")?;
|
||||
writeln!(f, "")?;
|
||||
writeln!(f, "- [Chapter 1](./chapter_1.md)")?;
|
||||
|
||||
let chapter_1 = src_dir.join("chapter_1.md");
|
||||
let mut f = File::create(&chapter_1).chain_err(|| "Unable to create chapter_1.md")?;
|
||||
writeln!(f, "# Chapter 1")?;
|
||||
if !summary.exists() {
|
||||
trace!("No summary found creating stub summary and chapter_1.md.");
|
||||
let mut f = File::create(&summary).with_context(|| "Unable to create SUMMARY.md")?;
|
||||
writeln!(f, "# Summary")?;
|
||||
writeln!(f)?;
|
||||
writeln!(f, "- [Chapter 1](./chapter_1.md)")?;
|
||||
|
||||
let chapter_1 = src_dir.join("chapter_1.md");
|
||||
let mut f =
|
||||
File::create(&chapter_1).with_context(|| "Unable to create chapter_1.md")?;
|
||||
writeln!(f, "# Chapter 1")?;
|
||||
} else {
|
||||
trace!("Existing summary found, no need to create stub files.");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
416
src/book/mod.rs
416
src/book/mod.rs
@@ -5,26 +5,30 @@
|
||||
//!
|
||||
//! [1]: ../index.html
|
||||
|
||||
mod summary;
|
||||
#[allow(clippy::module_inception)]
|
||||
mod book;
|
||||
mod init;
|
||||
mod summary;
|
||||
|
||||
pub use self::book::{load_book, Book, BookItem, BookItems, Chapter};
|
||||
pub use self::summary::{parse_summary, Link, SectionNumber, Summary, SummaryItem};
|
||||
pub use self::init::BookBuilder;
|
||||
pub use self::summary::{parse_summary, Link, SectionNumber, Summary, SummaryItem};
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use tempdir::TempDir;
|
||||
use std::string::ToString;
|
||||
use tempfile::Builder as TempFileBuilder;
|
||||
use toml::Value;
|
||||
|
||||
use utils;
|
||||
use renderer::{CmdRenderer, HtmlHandlebars, RenderContext, Renderer};
|
||||
use preprocess::{LinkPreprocessor, Preprocessor, PreprocessorContext};
|
||||
use errors::*;
|
||||
use crate::errors::*;
|
||||
use crate::preprocess::{
|
||||
CmdPreprocessor, IndexPreprocessor, LinkPreprocessor, Preprocessor, PreprocessorContext,
|
||||
};
|
||||
use crate::renderer::{CmdRenderer, HtmlHandlebars, MarkdownRenderer, RenderContext, Renderer};
|
||||
use crate::utils;
|
||||
|
||||
use config::Config;
|
||||
use crate::config::{Config, RustEdition};
|
||||
|
||||
/// The object used to manage and build a book.
|
||||
pub struct MDBook {
|
||||
@@ -34,10 +38,10 @@ pub struct MDBook {
|
||||
pub config: Config,
|
||||
/// A representation of the book's contents in memory.
|
||||
pub book: Book,
|
||||
renderers: Vec<Box<Renderer>>,
|
||||
renderers: Vec<Box<dyn Renderer>>,
|
||||
|
||||
/// List of pre-processors to be run on the book
|
||||
preprocessors: Vec<Box<Preprocessor>>,
|
||||
/// List of pre-processors to be run on the book.
|
||||
preprocessors: Vec<Box<dyn Preprocessor>>,
|
||||
}
|
||||
|
||||
impl MDBook {
|
||||
@@ -53,7 +57,7 @@ impl MDBook {
|
||||
warn!("This format is no longer used, so you should migrate to the");
|
||||
warn!("book.toml format.");
|
||||
warn!("Check the user guide for migration information:");
|
||||
warn!("\thttps://rust-lang-nursery.github.io/mdBook/format/config.html");
|
||||
warn!("\thttps://rust-lang.github.io/mdBook/format/config.html");
|
||||
}
|
||||
|
||||
let mut config = if config_location.exists() {
|
||||
@@ -65,7 +69,7 @@ impl MDBook {
|
||||
|
||||
config.update_from_env();
|
||||
|
||||
if log_enabled!(::log::Level::Trace) {
|
||||
if log_enabled!(log::Level::Trace) {
|
||||
for line in format!("Config: {:#?}", config).lines() {
|
||||
trace!("{}", line);
|
||||
}
|
||||
@@ -74,7 +78,7 @@ impl MDBook {
|
||||
MDBook::load_with_config(book_root, config)
|
||||
}
|
||||
|
||||
/// Load a book from its root directory using a custom config.
|
||||
/// Load a book from its root directory using a custom `Config`.
|
||||
pub fn load_with_config<P: Into<PathBuf>>(book_root: P, config: Config) -> Result<MDBook> {
|
||||
let root = book_root.into();
|
||||
|
||||
@@ -93,21 +97,42 @@ impl MDBook {
|
||||
})
|
||||
}
|
||||
|
||||
/// Load a book from its root directory using a custom `Config` and a custom summary.
|
||||
pub fn load_with_config_and_summary<P: Into<PathBuf>>(
|
||||
book_root: P,
|
||||
config: Config,
|
||||
summary: Summary,
|
||||
) -> Result<MDBook> {
|
||||
let root = book_root.into();
|
||||
|
||||
let src_dir = root.join(&config.book.src);
|
||||
let book = book::load_book_from_disk(&summary, &src_dir)?;
|
||||
|
||||
let renderers = determine_renderers(&config);
|
||||
let preprocessors = determine_preprocessors(&config)?;
|
||||
|
||||
Ok(MDBook {
|
||||
root,
|
||||
config,
|
||||
book,
|
||||
renderers,
|
||||
preprocessors,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns a flat depth-first iterator over the elements of the book,
|
||||
/// it returns an [BookItem enum](bookitem.html):
|
||||
/// it returns a [`BookItem`] enum:
|
||||
/// `(section: String, bookitem: &BookItem)`
|
||||
///
|
||||
/// ```no_run
|
||||
/// # extern crate mdbook;
|
||||
/// # use mdbook::MDBook;
|
||||
/// # use mdbook::book::BookItem;
|
||||
/// # #[allow(unused_variables)]
|
||||
/// # fn main() {
|
||||
/// # let book = MDBook::load("mybook").unwrap();
|
||||
/// for item in book.iter() {
|
||||
/// match *item {
|
||||
/// BookItem::Chapter(ref chapter) => {},
|
||||
/// BookItem::Separator => {},
|
||||
/// BookItem::PartTitle(ref title) => {}
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
@@ -118,9 +143,8 @@ impl MDBook {
|
||||
/// // 2. Chapter 2
|
||||
/// //
|
||||
/// // etc.
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn iter(&self) -> BookItems {
|
||||
pub fn iter(&self) -> BookItems<'_> {
|
||||
self.book.iter()
|
||||
}
|
||||
|
||||
@@ -149,58 +173,58 @@ impl MDBook {
|
||||
pub fn build(&self) -> Result<()> {
|
||||
info!("Book building has started");
|
||||
|
||||
let mut preprocessed_book = self.book.clone();
|
||||
let preprocess_ctx = PreprocessorContext::new(self.root.clone(), self.config.clone());
|
||||
|
||||
for preprocessor in &self.preprocessors {
|
||||
debug!("Running the {} preprocessor.", preprocessor.name());
|
||||
preprocessor.run(&preprocess_ctx, &mut preprocessed_book)?;
|
||||
}
|
||||
|
||||
for renderer in &self.renderers {
|
||||
info!("Running the {} backend", renderer.name());
|
||||
self.run_renderer(&preprocessed_book, renderer.as_ref())?;
|
||||
self.execute_build_process(&**renderer)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_renderer(&self, preprocessed_book: &Book, renderer: &Renderer) -> Result<()> {
|
||||
let name = renderer.name();
|
||||
let build_dir = self.build_dir_for(name);
|
||||
if build_dir.exists() {
|
||||
debug!(
|
||||
"Cleaning build dir for the \"{}\" renderer ({})",
|
||||
name,
|
||||
build_dir.display()
|
||||
);
|
||||
/// Run the entire build process for a particular [`Renderer`].
|
||||
pub fn execute_build_process(&self, renderer: &dyn Renderer) -> Result<()> {
|
||||
let mut preprocessed_book = self.book.clone();
|
||||
let preprocess_ctx = PreprocessorContext::new(
|
||||
self.root.clone(),
|
||||
self.config.clone(),
|
||||
renderer.name().to_string(),
|
||||
);
|
||||
|
||||
utils::fs::remove_dir_content(&build_dir)
|
||||
.chain_err(|| "Unable to clear output directory")?;
|
||||
for preprocessor in &self.preprocessors {
|
||||
if preprocessor_should_run(&**preprocessor, renderer, &self.config) {
|
||||
debug!("Running the {} preprocessor.", preprocessor.name());
|
||||
preprocessed_book = preprocessor.run(&preprocess_ctx, preprocessed_book)?;
|
||||
}
|
||||
}
|
||||
|
||||
let render_context = RenderContext::new(
|
||||
let name = renderer.name();
|
||||
let build_dir = self.build_dir_for(name);
|
||||
|
||||
let mut render_context = RenderContext::new(
|
||||
self.root.clone(),
|
||||
preprocessed_book.clone(),
|
||||
preprocessed_book,
|
||||
self.config.clone(),
|
||||
build_dir,
|
||||
);
|
||||
render_context
|
||||
.chapter_titles
|
||||
.extend(preprocess_ctx.chapter_titles.borrow_mut().drain());
|
||||
|
||||
info!("Running the {} backend", renderer.name());
|
||||
renderer
|
||||
.render(&render_context)
|
||||
.chain_err(|| "Rendering failed")
|
||||
.with_context(|| "Rendering failed")
|
||||
}
|
||||
|
||||
/// You can change the default renderer to another one by using this method.
|
||||
/// The only requirement is for your renderer to implement the [`Renderer`
|
||||
/// trait](../renderer/trait.Renderer.html)
|
||||
/// The only requirement is that your renderer implement the [`Renderer`]
|
||||
/// trait.
|
||||
pub fn with_renderer<R: Renderer + 'static>(&mut self, renderer: R) -> &mut Self {
|
||||
self.renderers.push(Box::new(renderer));
|
||||
self
|
||||
}
|
||||
|
||||
/// Register a [`Preprocessor`](../preprocess/trait.Preprocessor.html) to be used when rendering the book.
|
||||
pub fn with_preprecessor<P: Preprocessor + 'static>(&mut self, preprocessor: P) -> &mut Self {
|
||||
/// Register a [`Preprocessor`] to be used when rendering the book.
|
||||
pub fn with_preprocessor<P: Preprocessor + 'static>(&mut self, preprocessor: P) -> &mut Self {
|
||||
self.preprocessors.push(Box::new(preprocessor));
|
||||
self
|
||||
}
|
||||
@@ -213,39 +237,62 @@ impl MDBook {
|
||||
.flat_map(|x| vec![x.0, x.1])
|
||||
.collect();
|
||||
|
||||
let temp_dir = TempDir::new("mdbook")?;
|
||||
let temp_dir = TempFileBuilder::new().prefix("mdbook-").tempdir()?;
|
||||
|
||||
let preprocess_context = PreprocessorContext::new(self.root.clone(), self.config.clone());
|
||||
// FIXME: Is "test" the proper renderer name to use here?
|
||||
let preprocess_context =
|
||||
PreprocessorContext::new(self.root.clone(), self.config.clone(), "test".to_string());
|
||||
|
||||
LinkPreprocessor::new().run(&preprocess_context, &mut self.book)?;
|
||||
let book = LinkPreprocessor::new().run(&preprocess_context, self.book.clone())?;
|
||||
// Index Preprocessor is disabled so that chapter paths continue to point to the
|
||||
// actual markdown files.
|
||||
|
||||
for item in self.iter() {
|
||||
let mut failed = false;
|
||||
for item in book.iter() {
|
||||
if let BookItem::Chapter(ref ch) = *item {
|
||||
if !ch.path.as_os_str().is_empty() {
|
||||
let path = self.source_dir().join(&ch.path);
|
||||
let content = utils::fs::file_to_string(&path)?;
|
||||
info!("Testing file: {:?}", path);
|
||||
let chapter_path = match ch.path {
|
||||
Some(ref path) if !path.as_os_str().is_empty() => path,
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
// write preprocessed file to tempdir
|
||||
let path = temp_dir.path().join(&ch.path);
|
||||
let mut tmpf = utils::fs::create_file(&path)?;
|
||||
tmpf.write_all(content.as_bytes())?;
|
||||
let path = self.source_dir().join(&chapter_path);
|
||||
info!("Testing file: {:?}", path);
|
||||
|
||||
let output = Command::new("rustdoc")
|
||||
.arg(&path)
|
||||
.arg("--test")
|
||||
.args(&library_args)
|
||||
.output()?;
|
||||
// write preprocessed file to tempdir
|
||||
let path = temp_dir.path().join(&chapter_path);
|
||||
let mut tmpf = utils::fs::create_file(&path)?;
|
||||
tmpf.write_all(ch.content.as_bytes())?;
|
||||
|
||||
if !output.status.success() {
|
||||
bail!(ErrorKind::Subprocess(
|
||||
"Rustdoc returned an error".to_string(),
|
||||
output
|
||||
));
|
||||
let mut cmd = Command::new("rustdoc");
|
||||
cmd.arg(&path).arg("--test").args(&library_args);
|
||||
|
||||
if let Some(edition) = self.config.rust.edition {
|
||||
match edition {
|
||||
RustEdition::E2015 => {
|
||||
cmd.args(&["--edition", "2015"]);
|
||||
}
|
||||
RustEdition::E2018 => {
|
||||
cmd.args(&["--edition", "2018"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let output = cmd.output()?;
|
||||
|
||||
if !output.status.success() {
|
||||
failed = true;
|
||||
error!(
|
||||
"rustdoc returned an error:\n\
|
||||
\n--- stdout\n{}\n--- stderr\n{}",
|
||||
String::from_utf8_lossy(&output.stdout),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if failed {
|
||||
bail!("One or more tests failed");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -253,7 +300,7 @@ impl MDBook {
|
||||
/// artefacts.
|
||||
///
|
||||
/// If there is only 1 renderer, put it in the directory pointed to by the
|
||||
/// `build.build_dir` key in `Config`. If there is more than one then the
|
||||
/// `build.build_dir` key in [`Config`]. If there is more than one then the
|
||||
/// renderer gets its own directory within the main build dir.
|
||||
///
|
||||
/// i.e. If there were only one renderer (in this case, the HTML renderer):
|
||||
@@ -288,30 +335,29 @@ impl MDBook {
|
||||
self.root.join(&self.config.book.src)
|
||||
}
|
||||
|
||||
// FIXME: This really belongs as part of the `HtmlConfig`.
|
||||
#[doc(hidden)]
|
||||
/// Get the directory containing the theme resources for the book.
|
||||
pub fn theme_dir(&self) -> PathBuf {
|
||||
match self.config.html_config().and_then(|h| h.theme) {
|
||||
Some(d) => self.root.join(d),
|
||||
None => self.root.join("theme"),
|
||||
}
|
||||
self.config
|
||||
.html_config()
|
||||
.unwrap_or_default()
|
||||
.theme_dir(&self.root)
|
||||
}
|
||||
}
|
||||
|
||||
/// Look at the `Config` and try to figure out what renderers to use.
|
||||
fn determine_renderers(config: &Config) -> Vec<Box<Renderer>> {
|
||||
let mut renderers: Vec<Box<Renderer>> = Vec::new();
|
||||
fn determine_renderers(config: &Config) -> Vec<Box<dyn Renderer>> {
|
||||
let mut renderers = Vec::new();
|
||||
|
||||
if let Some(output_table) = config.get("output").and_then(|o| o.as_table()) {
|
||||
for (key, table) in output_table.iter() {
|
||||
// the "html" backend has its own Renderer
|
||||
if let Some(output_table) = config.get("output").and_then(Value::as_table) {
|
||||
renderers.extend(output_table.iter().map(|(key, table)| {
|
||||
if key == "html" {
|
||||
renderers.push(Box::new(HtmlHandlebars::new()));
|
||||
Box::new(HtmlHandlebars::new()) as Box<dyn Renderer>
|
||||
} else if key == "markdown" {
|
||||
Box::new(MarkdownRenderer::new()) as Box<dyn Renderer>
|
||||
} else {
|
||||
let renderer = interpret_custom_renderer(key, table);
|
||||
renderers.push(renderer);
|
||||
interpret_custom_renderer(key, table)
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
// if we couldn't find anything, add the HTML renderer as a default
|
||||
@@ -322,47 +368,98 @@ fn determine_renderers(config: &Config) -> Vec<Box<Renderer>> {
|
||||
renderers
|
||||
}
|
||||
|
||||
fn default_preprocessors() -> Vec<Box<Preprocessor>> {
|
||||
vec![Box::new(LinkPreprocessor::new())]
|
||||
fn default_preprocessors() -> Vec<Box<dyn Preprocessor>> {
|
||||
vec![
|
||||
Box::new(LinkPreprocessor::new()),
|
||||
Box::new(IndexPreprocessor::new()),
|
||||
]
|
||||
}
|
||||
|
||||
fn is_default_preprocessor(pre: &dyn Preprocessor) -> bool {
|
||||
let name = pre.name();
|
||||
name == LinkPreprocessor::NAME || name == IndexPreprocessor::NAME
|
||||
}
|
||||
|
||||
/// Look at the `MDBook` and try to figure out what preprocessors to run.
|
||||
fn determine_preprocessors(config: &Config) -> Result<Vec<Box<Preprocessor>>> {
|
||||
let preprocess_list = match config.build.preprocess {
|
||||
Some(ref p) => p,
|
||||
// If no preprocessor field is set, default to the LinkPreprocessor. This allows you
|
||||
// to disable the LinkPreprocessor by setting "preprocess" to an empty list.
|
||||
None => return Ok(default_preprocessors()),
|
||||
};
|
||||
fn determine_preprocessors(config: &Config) -> Result<Vec<Box<dyn Preprocessor>>> {
|
||||
let mut preprocessors = Vec::new();
|
||||
|
||||
let mut preprocessors: Vec<Box<Preprocessor>> = Vec::new();
|
||||
if config.build.use_default_preprocessors {
|
||||
preprocessors.extend(default_preprocessors());
|
||||
}
|
||||
|
||||
for key in preprocess_list {
|
||||
match key.as_ref() {
|
||||
"links" => preprocessors.push(Box::new(LinkPreprocessor::new())),
|
||||
_ => bail!("{:?} is not a recognised preprocessor", key),
|
||||
if let Some(preprocessor_table) = config.get("preprocessor").and_then(Value::as_table) {
|
||||
for key in preprocessor_table.keys() {
|
||||
match key.as_ref() {
|
||||
"links" => preprocessors.push(Box::new(LinkPreprocessor::new())),
|
||||
"index" => preprocessors.push(Box::new(IndexPreprocessor::new())),
|
||||
name => preprocessors.push(interpret_custom_preprocessor(
|
||||
name,
|
||||
&preprocessor_table[name],
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(preprocessors)
|
||||
}
|
||||
|
||||
fn interpret_custom_renderer(key: &str, table: &Value) -> Box<Renderer> {
|
||||
fn interpret_custom_preprocessor(key: &str, table: &Value) -> Box<CmdPreprocessor> {
|
||||
let command = table
|
||||
.get("command")
|
||||
.and_then(Value::as_str)
|
||||
.map(ToString::to_string)
|
||||
.unwrap_or_else(|| format!("mdbook-{}", key));
|
||||
|
||||
Box::new(CmdPreprocessor::new(key.to_string(), command))
|
||||
}
|
||||
|
||||
fn interpret_custom_renderer(key: &str, table: &Value) -> Box<CmdRenderer> {
|
||||
// look for the `command` field, falling back to using the key
|
||||
// prepended by "mdbook-"
|
||||
let table_dot_command = table
|
||||
.get("command")
|
||||
.and_then(|c| c.as_str())
|
||||
.map(|s| s.to_string());
|
||||
.and_then(Value::as_str)
|
||||
.map(ToString::to_string);
|
||||
|
||||
let command = table_dot_command.unwrap_or_else(|| format!("mdbook-{}", key));
|
||||
|
||||
Box::new(CmdRenderer::new(key.to_string(), command.to_string()))
|
||||
Box::new(CmdRenderer::new(key.to_string(), command))
|
||||
}
|
||||
|
||||
/// Check whether we should run a particular `Preprocessor` in combination
|
||||
/// with the renderer, falling back to `Preprocessor::supports_renderer()`
|
||||
/// method if the user doesn't say anything.
|
||||
///
|
||||
/// The `build.use-default-preprocessors` config option can be used to ensure
|
||||
/// default preprocessors always run if they support the renderer.
|
||||
fn preprocessor_should_run(
|
||||
preprocessor: &dyn Preprocessor,
|
||||
renderer: &dyn Renderer,
|
||||
cfg: &Config,
|
||||
) -> bool {
|
||||
// default preprocessors should be run by default (if supported)
|
||||
if cfg.build.use_default_preprocessors && is_default_preprocessor(preprocessor) {
|
||||
return preprocessor.supports_renderer(renderer.name());
|
||||
}
|
||||
|
||||
let key = format!("preprocessor.{}.renderers", preprocessor.name());
|
||||
let renderer_name = renderer.name();
|
||||
|
||||
if let Some(Value::Array(ref explicit_renderers)) = cfg.get(&key) {
|
||||
return explicit_renderers
|
||||
.iter()
|
||||
.filter_map(Value::as_str)
|
||||
.any(|name| name == renderer_name);
|
||||
}
|
||||
|
||||
preprocessor.supports_renderer(renderer_name)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::str::FromStr;
|
||||
use toml::value::{Table, Value};
|
||||
|
||||
#[test]
|
||||
@@ -404,61 +501,120 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_defaults_to_link_preprocessor_if_not_set() {
|
||||
fn config_defaults_to_link_and_index_preprocessor_if_not_set() {
|
||||
let cfg = Config::default();
|
||||
|
||||
// make sure we haven't got anything in the `output` table
|
||||
assert!(cfg.build.preprocess.is_none());
|
||||
// make sure we haven't got anything in the `preprocessor` table
|
||||
assert!(cfg.get("preprocessor").is_none());
|
||||
|
||||
let got = determine_preprocessors(&cfg);
|
||||
|
||||
assert!(got.is_ok());
|
||||
assert_eq!(got.as_ref().unwrap().len(), 1);
|
||||
assert_eq!(got.as_ref().unwrap().len(), 2);
|
||||
assert_eq!(got.as_ref().unwrap()[0].name(), "links");
|
||||
assert_eq!(got.as_ref().unwrap()[1].name(), "index");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_doesnt_default_if_empty() {
|
||||
let cfg_str: &'static str = r#"
|
||||
fn use_default_preprocessors_works() {
|
||||
let mut cfg = Config::default();
|
||||
cfg.build.use_default_preprocessors = false;
|
||||
|
||||
let got = determine_preprocessors(&cfg).unwrap();
|
||||
|
||||
assert_eq!(got.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_determine_third_party_preprocessors() {
|
||||
let cfg_str = r#"
|
||||
[book]
|
||||
title = "Some Book"
|
||||
|
||||
[preprocessor.random]
|
||||
|
||||
[build]
|
||||
build-dir = "outputs"
|
||||
create-missing = false
|
||||
preprocess = []
|
||||
"#;
|
||||
|
||||
let cfg = Config::from_str(cfg_str).unwrap();
|
||||
|
||||
// make sure we have something in the `output` table
|
||||
assert!(cfg.build.preprocess.is_some());
|
||||
// make sure the `preprocessor.random` table exists
|
||||
assert!(cfg.get_preprocessor("random").is_some());
|
||||
|
||||
let got = determine_preprocessors(&cfg);
|
||||
let got = determine_preprocessors(&cfg).unwrap();
|
||||
|
||||
assert!(got.is_ok());
|
||||
assert!(got.unwrap().is_empty());
|
||||
assert!(got.into_iter().any(|p| p.name() == "random"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_complains_if_unimplemented_preprocessor() {
|
||||
let cfg_str: &'static str = r#"
|
||||
[book]
|
||||
title = "Some Book"
|
||||
|
||||
[build]
|
||||
build-dir = "outputs"
|
||||
create-missing = false
|
||||
preprocess = ["random"]
|
||||
fn preprocessors_can_provide_their_own_commands() {
|
||||
let cfg_str = r#"
|
||||
[preprocessor.random]
|
||||
command = "python random.py"
|
||||
"#;
|
||||
|
||||
let cfg = Config::from_str(cfg_str).unwrap();
|
||||
|
||||
// make sure we have something in the `output` table
|
||||
assert!(cfg.build.preprocess.is_some());
|
||||
// make sure the `preprocessor.random` table exists
|
||||
let random = cfg.get_preprocessor("random").unwrap();
|
||||
let random = interpret_custom_preprocessor("random", &Value::Table(random.clone()));
|
||||
|
||||
let got = determine_preprocessors(&cfg);
|
||||
assert_eq!(random.cmd(), "python random.py");
|
||||
}
|
||||
|
||||
assert!(got.is_err());
|
||||
#[test]
|
||||
fn config_respects_preprocessor_selection() {
|
||||
let cfg_str = r#"
|
||||
[preprocessor.links]
|
||||
renderers = ["html"]
|
||||
"#;
|
||||
|
||||
let cfg = Config::from_str(cfg_str).unwrap();
|
||||
|
||||
// double-check that we can access preprocessor.links.renderers[0]
|
||||
let html = cfg
|
||||
.get_preprocessor("links")
|
||||
.and_then(|links| links.get("renderers"))
|
||||
.and_then(Value::as_array)
|
||||
.and_then(|renderers| renderers.get(0))
|
||||
.and_then(Value::as_str)
|
||||
.unwrap();
|
||||
assert_eq!(html, "html");
|
||||
let html_renderer = HtmlHandlebars::default();
|
||||
let pre = LinkPreprocessor::new();
|
||||
|
||||
let should_run = preprocessor_should_run(&pre, &html_renderer, &cfg);
|
||||
assert!(should_run);
|
||||
}
|
||||
|
||||
struct BoolPreprocessor(bool);
|
||||
impl Preprocessor for BoolPreprocessor {
|
||||
fn name(&self) -> &str {
|
||||
"bool-preprocessor"
|
||||
}
|
||||
|
||||
fn run(&self, _ctx: &PreprocessorContext, _book: Book) -> Result<Book> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn supports_renderer(&self, _renderer: &str) -> bool {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preprocessor_should_run_falls_back_to_supports_renderer_method() {
|
||||
let cfg = Config::default();
|
||||
let html = HtmlHandlebars::new();
|
||||
|
||||
let should_be = true;
|
||||
let got = preprocessor_should_run(&BoolPreprocessor(should_be), &html, &cfg);
|
||||
assert_eq!(got, should_be);
|
||||
|
||||
let should_be = false;
|
||||
let got = preprocessor_should_run(&BoolPreprocessor(should_be), &html, &cfg);
|
||||
assert_eq!(got, should_be);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,21 +1,22 @@
|
||||
use std::path::PathBuf;
|
||||
use crate::{get_book_dir, open};
|
||||
use clap::{App, ArgMatches, SubCommand};
|
||||
use mdbook::MDBook;
|
||||
use mdbook::errors::Result;
|
||||
use {get_book_dir, open};
|
||||
use mdbook::MDBook;
|
||||
|
||||
// Create clap subcommand arguments
|
||||
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
|
||||
SubCommand::with_name("build")
|
||||
.about("Build the book from the markdown files")
|
||||
.arg_from_usage("-o, --open 'Open the compiled book in a web browser'")
|
||||
.about("Builds a book from its markdown files")
|
||||
.arg_from_usage(
|
||||
"-d, --dest-dir=[dest-dir] 'The output directory for your book{n}(Defaults to ./book \
|
||||
when omitted)'",
|
||||
"-d, --dest-dir=[dest-dir] 'Output directory for the book{n}\
|
||||
Relative paths are interpreted relative to the book's root directory.{n}\
|
||||
If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.'",
|
||||
)
|
||||
.arg_from_usage(
|
||||
"[dir] 'A directory for your book{n}(Defaults to Current Directory when omitted)'",
|
||||
"[dir] 'Root directory for the book{n}\
|
||||
(Defaults to the Current Directory when omitted)'",
|
||||
)
|
||||
.arg_from_usage("-o, --open 'Opens the compiled book in a web browser'")
|
||||
}
|
||||
|
||||
// Build command implementation
|
||||
@@ -24,7 +25,7 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
|
||||
let mut book = MDBook::load(&book_dir)?;
|
||||
|
||||
if let Some(dest_dir) = args.value_of("dest-dir") {
|
||||
book.config.build.build_dir = PathBuf::from(dest_dir);
|
||||
book.config.build.build_dir = dest_dir.into();
|
||||
}
|
||||
|
||||
book.build()?;
|
||||
39
src/cmd/clean.rs
Normal file
39
src/cmd/clean.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
use crate::get_book_dir;
|
||||
use anyhow::Context;
|
||||
use clap::{App, ArgMatches, SubCommand};
|
||||
use mdbook::MDBook;
|
||||
use std::fs;
|
||||
|
||||
// Create clap subcommand arguments
|
||||
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
|
||||
SubCommand::with_name("clean")
|
||||
.about("Deletes a built book")
|
||||
.arg_from_usage(
|
||||
"-d, --dest-dir=[dest-dir] 'Output directory for the book{n}\
|
||||
Relative paths are interpreted relative to the book's root directory.{n}\
|
||||
Running this command deletes this directory.{n}\
|
||||
If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.'",
|
||||
)
|
||||
.arg_from_usage(
|
||||
"[dir] 'Root directory for the book{n}\
|
||||
(Defaults to the Current Directory when omitted)'",
|
||||
)
|
||||
}
|
||||
|
||||
// Clean command implementation
|
||||
pub fn execute(args: &ArgMatches) -> mdbook::errors::Result<()> {
|
||||
let book_dir = get_book_dir(args);
|
||||
let book = MDBook::load(&book_dir)?;
|
||||
|
||||
let dir_to_remove = match args.value_of("dest-dir") {
|
||||
Some(dest_dir) => dest_dir.into(),
|
||||
None => book.root.join(&book.config.build.build_dir),
|
||||
};
|
||||
|
||||
if dir_to_remove.exists() {
|
||||
fs::remove_dir_all(&dir_to_remove)
|
||||
.with_context(|| "Unable to remove the build directory")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
105
src/cmd/init.rs
Normal file
105
src/cmd/init.rs
Normal file
@@ -0,0 +1,105 @@
|
||||
use crate::get_book_dir;
|
||||
use clap::{App, ArgMatches, SubCommand};
|
||||
use mdbook::config;
|
||||
use mdbook::errors::Result;
|
||||
use mdbook::MDBook;
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
use std::process::Command;
|
||||
|
||||
// Create clap subcommand arguments
|
||||
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
|
||||
SubCommand::with_name("init")
|
||||
.about("Creates the boilerplate structure and files for a new book")
|
||||
// the {n} denotes a newline which will properly aligned in all help messages
|
||||
.arg_from_usage(
|
||||
"[dir] 'Directory to create the book in{n}\
|
||||
(Defaults to the Current Directory when omitted)'",
|
||||
)
|
||||
.arg_from_usage("--theme 'Copies the default theme into your source folder'")
|
||||
.arg_from_usage("--force 'Skips confirmation prompts'")
|
||||
}
|
||||
|
||||
// Init command implementation
|
||||
pub fn execute(args: &ArgMatches) -> Result<()> {
|
||||
let book_dir = get_book_dir(args);
|
||||
let mut builder = MDBook::init(&book_dir);
|
||||
let mut config = config::Config::default();
|
||||
|
||||
// If flag `--theme` is present, copy theme to src
|
||||
if args.is_present("theme") {
|
||||
let theme_dir = book_dir.join("theme");
|
||||
println!();
|
||||
println!("Copying the default theme to {}", theme_dir.display());
|
||||
// Skip this if `--force` is present
|
||||
if !args.is_present("force") && theme_dir.exists() {
|
||||
println!("This could potentially overwrite files already present in that directory.");
|
||||
print!("\nAre you sure you want to continue? (y/n) ");
|
||||
|
||||
// Read answer from user and exit if it's not 'yes'
|
||||
if confirm() {
|
||||
builder.copy_theme(true);
|
||||
}
|
||||
} else {
|
||||
builder.copy_theme(true);
|
||||
}
|
||||
}
|
||||
|
||||
println!("\nDo you want a .gitignore to be created? (y/n)");
|
||||
|
||||
if confirm() {
|
||||
builder.create_gitignore(true);
|
||||
}
|
||||
|
||||
config.book.title = request_book_title();
|
||||
|
||||
if let Some(author) = get_author_name() {
|
||||
debug!("Obtained user name from gitconfig: {:?}", author);
|
||||
config.book.authors.push(author);
|
||||
builder.with_config(config);
|
||||
}
|
||||
|
||||
builder.build()?;
|
||||
println!("\nAll done, no errors...");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Obtains author name from git config file by running the `git config` command.
|
||||
fn get_author_name() -> Option<String> {
|
||||
let output = Command::new("git")
|
||||
.args(&["config", "--get", "user.name"])
|
||||
.output()
|
||||
.ok()?;
|
||||
|
||||
if output.status.success() {
|
||||
Some(String::from_utf8_lossy(&output.stdout).trim().to_owned())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Request book title from user and return if provided.
|
||||
fn request_book_title() -> Option<String> {
|
||||
println!("What title would you like to give the book? ");
|
||||
io::stdout().flush().unwrap();
|
||||
let mut resp = String::new();
|
||||
io::stdin().read_line(&mut resp).unwrap();
|
||||
let resp = resp.trim();
|
||||
if resp.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(resp.into())
|
||||
}
|
||||
}
|
||||
|
||||
// Simple function for user confirmation
|
||||
fn confirm() -> bool {
|
||||
io::stdout().flush().unwrap();
|
||||
let mut s = String::new();
|
||||
io::stdin().read_line(&mut s).ok();
|
||||
match &*s.trim() {
|
||||
"Y" | "y" | "yes" | "Yes" => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
10
src/cmd/mod.rs
Normal file
10
src/cmd/mod.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
//! Subcommand modules for the `mdbook` binary.
|
||||
|
||||
pub mod build;
|
||||
pub mod clean;
|
||||
pub mod init;
|
||||
#[cfg(feature = "serve")]
|
||||
pub mod serve;
|
||||
pub mod test;
|
||||
#[cfg(feature = "watch")]
|
||||
pub mod watch;
|
||||
172
src/cmd/serve.rs
Normal file
172
src/cmd/serve.rs
Normal file
@@ -0,0 +1,172 @@
|
||||
#[cfg(feature = "watch")]
|
||||
use super::watch;
|
||||
use crate::{get_book_dir, open};
|
||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||
use futures_util::sink::SinkExt;
|
||||
use futures_util::StreamExt;
|
||||
use mdbook::errors::*;
|
||||
use mdbook::utils;
|
||||
use mdbook::utils::fs::get_404_output_file;
|
||||
use mdbook::MDBook;
|
||||
use std::net::{SocketAddr, ToSocketAddrs};
|
||||
use std::path::PathBuf;
|
||||
use tokio::sync::broadcast;
|
||||
use warp::ws::Message;
|
||||
use warp::Filter;
|
||||
|
||||
/// The HTTP endpoint for the websocket used to trigger reloads when a file changes.
|
||||
const LIVE_RELOAD_ENDPOINT: &str = "__livereload";
|
||||
|
||||
// Create clap subcommand arguments
|
||||
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
|
||||
SubCommand::with_name("serve")
|
||||
.about("Serves a book at http://localhost:3000, and rebuilds it on changes")
|
||||
.arg_from_usage(
|
||||
"-d, --dest-dir=[dest-dir] 'Output directory for the book{n}\
|
||||
Relative paths are interpreted relative to the book's root directory.{n}\
|
||||
If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.'",
|
||||
)
|
||||
.arg_from_usage(
|
||||
"[dir] 'Root directory for the book{n}\
|
||||
(Defaults to the Current Directory when omitted)'",
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("hostname")
|
||||
.short("n")
|
||||
.long("hostname")
|
||||
.takes_value(true)
|
||||
.default_value("localhost")
|
||||
.empty_values(false)
|
||||
.help("Hostname to listen on for HTTP connections"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("port")
|
||||
.short("p")
|
||||
.long("port")
|
||||
.takes_value(true)
|
||||
.default_value("3000")
|
||||
.empty_values(false)
|
||||
.help("Port to use for HTTP connections"),
|
||||
)
|
||||
.arg_from_usage("-o, --open 'Opens the book server in a web browser'")
|
||||
}
|
||||
|
||||
// Serve command implementation
|
||||
pub fn execute(args: &ArgMatches) -> Result<()> {
|
||||
let book_dir = get_book_dir(args);
|
||||
let mut book = MDBook::load(&book_dir)?;
|
||||
|
||||
let port = args.value_of("port").unwrap();
|
||||
let hostname = args.value_of("hostname").unwrap();
|
||||
let open_browser = args.is_present("open");
|
||||
|
||||
let address = format!("{}:{}", hostname, port);
|
||||
|
||||
let livereload_url = format!("ws://{}/{}", address, LIVE_RELOAD_ENDPOINT);
|
||||
let update_config = |book: &mut MDBook| {
|
||||
book.config
|
||||
.set("output.html.livereload-url", &livereload_url)
|
||||
.expect("livereload-url update failed");
|
||||
if let Some(dest_dir) = args.value_of("dest-dir") {
|
||||
book.config.build.build_dir = dest_dir.into();
|
||||
}
|
||||
// Override site-url for local serving of the 404 file
|
||||
book.config.set("output.html.site-url", "/").unwrap();
|
||||
};
|
||||
update_config(&mut book);
|
||||
book.build()?;
|
||||
|
||||
let sockaddr: SocketAddr = address
|
||||
.to_socket_addrs()?
|
||||
.next()
|
||||
.ok_or_else(|| anyhow::anyhow!("no address found for {}", address))?;
|
||||
let build_dir = book.build_dir_for("html");
|
||||
let input_404 = book
|
||||
.config
|
||||
.get("output.html.input-404")
|
||||
.map(toml::Value::as_str)
|
||||
.and_then(std::convert::identity) // flatten
|
||||
.map(ToString::to_string);
|
||||
let file_404 = get_404_output_file(&input_404);
|
||||
|
||||
// A channel used to broadcast to any websockets to reload when a file changes.
|
||||
let (tx, _rx) = tokio::sync::broadcast::channel::<Message>(100);
|
||||
|
||||
let reload_tx = tx.clone();
|
||||
let thread_handle = std::thread::spawn(move || {
|
||||
serve(build_dir, sockaddr, reload_tx, &file_404);
|
||||
});
|
||||
|
||||
let serving_url = format!("http://{}", address);
|
||||
info!("Serving on: {}", serving_url);
|
||||
|
||||
if open_browser {
|
||||
open(serving_url);
|
||||
}
|
||||
|
||||
#[cfg(feature = "watch")]
|
||||
watch::trigger_on_change(&book, move |paths, book_dir| {
|
||||
info!("Files changed: {:?}", paths);
|
||||
info!("Building book...");
|
||||
|
||||
// FIXME: This area is really ugly because we need to re-set livereload :(
|
||||
let result = MDBook::load(&book_dir).and_then(|mut b| {
|
||||
update_config(&mut b);
|
||||
b.build()
|
||||
});
|
||||
|
||||
if let Err(e) = result {
|
||||
error!("Unable to load the book");
|
||||
utils::log_backtrace(&e);
|
||||
} else {
|
||||
let _ = tx.send(Message::text("reload"));
|
||||
}
|
||||
});
|
||||
|
||||
let _ = thread_handle.join();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn serve(
|
||||
build_dir: PathBuf,
|
||||
address: SocketAddr,
|
||||
reload_tx: broadcast::Sender<Message>,
|
||||
file_404: &str,
|
||||
) {
|
||||
// A warp Filter which captures `reload_tx` and provides an `rx` copy to
|
||||
// receive reload messages.
|
||||
let sender = warp::any().map(move || reload_tx.subscribe());
|
||||
|
||||
// A warp Filter to handle the livereload endpoint. This upgrades to a
|
||||
// websocket, and then waits for any filesystem change notifications, and
|
||||
// relays them over the websocket.
|
||||
let livereload = warp::path(LIVE_RELOAD_ENDPOINT)
|
||||
.and(warp::ws())
|
||||
.and(sender)
|
||||
.map(|ws: warp::ws::Ws, mut rx: broadcast::Receiver<Message>| {
|
||||
ws.on_upgrade(move |ws| async move {
|
||||
let (mut user_ws_tx, _user_ws_rx) = ws.split();
|
||||
trace!("websocket got connection");
|
||||
if let Ok(m) = rx.recv().await {
|
||||
trace!("notify of reload");
|
||||
let _ = user_ws_tx.send(m).await;
|
||||
}
|
||||
})
|
||||
});
|
||||
// A warp Filter that serves from the filesystem.
|
||||
let book_route = warp::fs::dir(build_dir.clone());
|
||||
// The fallback route for 404 errors
|
||||
let fallback_route = warp::fs::file(build_dir.join(file_404))
|
||||
.map(|reply| warp::reply::with_status(reply, warp::http::StatusCode::NOT_FOUND));
|
||||
let routes = livereload.or(book_route).or(fallback_route);
|
||||
|
||||
std::panic::set_hook(Box::new(move |panic_info| {
|
||||
// exit if serve panics
|
||||
error!("Unable to serve: {}", panic_info);
|
||||
std::process::exit(1);
|
||||
}));
|
||||
|
||||
warp::serve(routes).run(address).await;
|
||||
}
|
||||
46
src/cmd/test.rs
Normal file
46
src/cmd/test.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
use crate::get_book_dir;
|
||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||
use mdbook::errors::Result;
|
||||
use mdbook::MDBook;
|
||||
|
||||
// Create clap subcommand arguments
|
||||
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
|
||||
SubCommand::with_name("test")
|
||||
.about("Tests that a book's Rust code samples compile")
|
||||
.arg_from_usage(
|
||||
"-d, --dest-dir=[dest-dir] 'Output directory for the book{n}\
|
||||
Relative paths are interpreted relative to the book's root directory.{n}\
|
||||
If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.'",
|
||||
)
|
||||
.arg_from_usage(
|
||||
"[dir] 'Root directory for the book{n}\
|
||||
(Defaults to the Current Directory when omitted)'",
|
||||
)
|
||||
.arg(Arg::with_name("library-path")
|
||||
.short("L")
|
||||
.long("library-path")
|
||||
.value_name("dir")
|
||||
.takes_value(true)
|
||||
.require_delimiter(true)
|
||||
.multiple(true)
|
||||
.empty_values(false)
|
||||
.help("A comma-separated list of directories to add to {n}the crate search path when building tests"))
|
||||
}
|
||||
|
||||
// test command implementation
|
||||
pub fn execute(args: &ArgMatches) -> Result<()> {
|
||||
let library_paths: Vec<&str> = args
|
||||
.values_of("library-path")
|
||||
.map(std::iter::Iterator::collect)
|
||||
.unwrap_or_default();
|
||||
let book_dir = get_book_dir(args);
|
||||
let mut book = MDBook::load(&book_dir)?;
|
||||
|
||||
if let Some(dest_dir) = args.value_of("dest-dir") {
|
||||
book.config.build.build_dir = dest_dir.into();
|
||||
}
|
||||
|
||||
book.test(library_paths)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
164
src/cmd/watch.rs
Normal file
164
src/cmd/watch.rs
Normal file
@@ -0,0 +1,164 @@
|
||||
use crate::{get_book_dir, open};
|
||||
use clap::{App, ArgMatches, SubCommand};
|
||||
use mdbook::errors::Result;
|
||||
use mdbook::utils;
|
||||
use mdbook::MDBook;
|
||||
use notify::Watcher;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::mpsc::channel;
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
|
||||
// Create clap subcommand arguments
|
||||
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
|
||||
SubCommand::with_name("watch")
|
||||
.about("Watches a book's files and rebuilds it on changes")
|
||||
.arg_from_usage(
|
||||
"-d, --dest-dir=[dest-dir] 'Output directory for the book{n}\
|
||||
Relative paths are interpreted relative to the book's root directory.{n}\
|
||||
If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.'",
|
||||
)
|
||||
.arg_from_usage(
|
||||
"[dir] 'Root directory for the book{n}\
|
||||
(Defaults to the Current Directory when omitted)'",
|
||||
)
|
||||
.arg_from_usage("-o, --open 'Open the compiled book in a web browser'")
|
||||
}
|
||||
|
||||
// Watch command implementation
|
||||
pub fn execute(args: &ArgMatches) -> Result<()> {
|
||||
let book_dir = get_book_dir(args);
|
||||
let mut book = MDBook::load(&book_dir)?;
|
||||
|
||||
let update_config = |book: &mut MDBook| {
|
||||
if let Some(dest_dir) = args.value_of("dest-dir") {
|
||||
book.config.build.build_dir = dest_dir.into();
|
||||
}
|
||||
};
|
||||
update_config(&mut book);
|
||||
|
||||
if args.is_present("open") {
|
||||
book.build()?;
|
||||
open(book.build_dir_for("html").join("index.html"));
|
||||
}
|
||||
|
||||
trigger_on_change(&book, |paths, book_dir| {
|
||||
info!("Files changed: {:?}\nBuilding book...\n", paths);
|
||||
let result = MDBook::load(&book_dir).and_then(|mut b| {
|
||||
update_config(&mut b);
|
||||
b.build()
|
||||
});
|
||||
|
||||
if let Err(e) = result {
|
||||
error!("Unable to build the book");
|
||||
utils::log_backtrace(&e);
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove_ignored_files(book_root: &Path, paths: &[PathBuf]) -> Vec<PathBuf> {
|
||||
if paths.is_empty() {
|
||||
return vec![];
|
||||
}
|
||||
|
||||
match find_gitignore(book_root) {
|
||||
Some(gitignore_path) => {
|
||||
match gitignore::File::new(gitignore_path.as_path()) {
|
||||
Ok(exclusion_checker) => filter_ignored_files(exclusion_checker, paths),
|
||||
Err(_) => {
|
||||
// We're unable to read the .gitignore file, so we'll silently allow everything.
|
||||
// Please see discussion: https://github.com/rust-lang/mdBook/pull/1051
|
||||
paths.iter().map(|path| path.to_path_buf()).collect()
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
// There is no .gitignore file.
|
||||
paths.iter().map(|path| path.to_path_buf()).collect()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn find_gitignore(book_root: &Path) -> Option<PathBuf> {
|
||||
book_root
|
||||
.ancestors()
|
||||
.map(|p| p.join(".gitignore"))
|
||||
.find(|p| p.exists())
|
||||
}
|
||||
|
||||
fn filter_ignored_files(exclusion_checker: gitignore::File, paths: &[PathBuf]) -> Vec<PathBuf> {
|
||||
paths
|
||||
.iter()
|
||||
.filter(|path| match exclusion_checker.is_excluded(path) {
|
||||
Ok(exclude) => !exclude,
|
||||
Err(error) => {
|
||||
warn!(
|
||||
"Unable to determine if {:?} is excluded: {:?}. Including it.",
|
||||
&path, error
|
||||
);
|
||||
true
|
||||
}
|
||||
})
|
||||
.map(|path| path.to_path_buf())
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Calls the closure when a book source file is changed, blocking indefinitely.
|
||||
pub fn trigger_on_change<F>(book: &MDBook, closure: F)
|
||||
where
|
||||
F: Fn(Vec<PathBuf>, &Path),
|
||||
{
|
||||
use notify::DebouncedEvent::*;
|
||||
use notify::RecursiveMode::*;
|
||||
|
||||
// Create a channel to receive the events.
|
||||
let (tx, rx) = channel();
|
||||
|
||||
let mut watcher = match notify::watcher(tx, Duration::from_secs(1)) {
|
||||
Ok(w) => w,
|
||||
Err(e) => {
|
||||
error!("Error while trying to watch the files:\n\n\t{:?}", e);
|
||||
std::process::exit(1)
|
||||
}
|
||||
};
|
||||
|
||||
// Add the source directory to the watcher
|
||||
if let Err(e) = watcher.watch(book.source_dir(), Recursive) {
|
||||
error!("Error while watching {:?}:\n {:?}", book.source_dir(), e);
|
||||
std::process::exit(1);
|
||||
};
|
||||
|
||||
let _ = watcher.watch(book.theme_dir(), Recursive);
|
||||
|
||||
// Add the book.toml file to the watcher if it exists
|
||||
let _ = watcher.watch(book.root.join("book.toml"), NonRecursive);
|
||||
|
||||
info!("Listening for changes...");
|
||||
|
||||
loop {
|
||||
let first_event = rx.recv().unwrap();
|
||||
sleep(Duration::from_millis(50));
|
||||
let other_events = rx.try_iter();
|
||||
|
||||
let all_events = std::iter::once(first_event).chain(other_events);
|
||||
|
||||
let paths = all_events
|
||||
.filter_map(|event| {
|
||||
debug!("Received filesystem event: {:?}", event);
|
||||
|
||||
match event {
|
||||
Create(path) | Write(path) | Remove(path) | Rename(_, path) => Some(path),
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let paths = remove_ignored_files(&book.root, &paths[..]);
|
||||
|
||||
if !paths.is_empty() {
|
||||
closure(paths, &book.root);
|
||||
}
|
||||
}
|
||||
}
|
||||
673
src/config.rs
673
src/config.rs
File diff suppressed because it is too large
Load Diff
108
src/lib.rs
108
src/lib.rs
@@ -1,7 +1,8 @@
|
||||
//! # mdBook
|
||||
//!
|
||||
//! **mdBook** is similar to GitBook but implemented in Rust.
|
||||
//! It offers a command line interface, but can also be used as a regular crate.
|
||||
//! **mdBook** is a tool for rendering a collection of markdown documents into
|
||||
//! a form more suitable for end users like HTML or EPUB. It offers a command
|
||||
//! line interface, but this crate can be used if more control is required.
|
||||
//!
|
||||
//! This is the API doc, the [user guide] is also available if you want
|
||||
//! information about the command line tool, format, structure etc. It is also
|
||||
@@ -15,6 +16,12 @@
|
||||
//! - Accessing the public API to help create a new Renderer
|
||||
//! - ...
|
||||
//!
|
||||
//! > **Note:** While we try to ensure `mdbook`'s command-line interface and
|
||||
//! > behaviour are backwards compatible, the tool's internals are still
|
||||
//! > evolving and being iterated on. If you wish to prevent accidental
|
||||
//! > breakages it is recommended to pin any tools building on top of the
|
||||
//! > `mdbook` crate to a specific release.
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! If creating a new book from scratch, you'll want to get a `BookBuilder` via
|
||||
@@ -52,107 +59,64 @@
|
||||
//!
|
||||
//! ## Implementing a new Backend
|
||||
//!
|
||||
//! `mdbook` has a fairly flexible mechanism for creating additional backends
|
||||
//! `mdbook` has a fairly flexible mechanism for creating additional backends
|
||||
//! for your book. The general idea is you'll add an extra table in the book's
|
||||
//! `book.toml` which specifies an executable to be invoked by `mdbook`. This
|
||||
//! executable will then be called during a build, with an in-memory
|
||||
//! executable will then be called during a build, with an in-memory
|
||||
//! representation ([`RenderContext`]) of the book being passed to the
|
||||
//! subprocess via `stdin`.
|
||||
//!
|
||||
//! The [`RenderContext`] gives the backend access to the contents of
|
||||
//! subprocess via `stdin`.
|
||||
//!
|
||||
//! The [`RenderContext`] gives the backend access to the contents of
|
||||
//! `book.toml` and lets it know which directory all generated artefacts should
|
||||
//! be placed in. For a much more in-depth explanation, consult the [relevant
|
||||
//! chapter] in the *For Developers* section of the user guide.
|
||||
//!
|
||||
//! To make creating a backend easier, the `mdbook` crate can be imported
|
||||
//! directly, making deserializing the `RenderContext` easy and giving you
|
||||
//!
|
||||
//! To make creating a backend easier, the `mdbook` crate can be imported
|
||||
//! directly, making deserializing the `RenderContext` easy and giving you
|
||||
//! access to the various methods for working with the [`Config`].
|
||||
//!
|
||||
//! [user guide]: https://rust-lang-nursery.github.io/mdBook/
|
||||
//! [`RenderContext`]: renderer/struct.RenderContext.html
|
||||
//! [relevant chapter]: https://rust-lang-nursery.github.io/mdBook/for_developers/backends.html
|
||||
//! [`Config`]: config/struct.Config.html
|
||||
//! [user guide]: https://rust-lang.github.io/mdBook/
|
||||
//! [`RenderContext`]: renderer::RenderContext
|
||||
//! [relevant chapter]: https://rust-lang.github.io/mdBook/for_developers/backends.html
|
||||
//! [`Config`]: config::Config
|
||||
|
||||
#![deny(missing_docs)]
|
||||
#![deny(rust_2018_idioms)]
|
||||
#![allow(clippy::comparison_chain)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate error_chain;
|
||||
extern crate handlebars;
|
||||
extern crate itertools;
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate memchr;
|
||||
extern crate pulldown_cmark;
|
||||
extern crate regex;
|
||||
extern crate serde;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
#[macro_use]
|
||||
extern crate serde_json;
|
||||
extern crate shlex;
|
||||
extern crate tempdir;
|
||||
extern crate toml;
|
||||
extern crate toml_query;
|
||||
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
extern crate pretty_assertions;
|
||||
|
||||
pub mod preprocess;
|
||||
pub mod book;
|
||||
pub mod config;
|
||||
pub mod preprocess;
|
||||
pub mod renderer;
|
||||
pub mod theme;
|
||||
pub mod utils;
|
||||
|
||||
pub use book::MDBook;
|
||||
pub use book::BookItem;
|
||||
pub use renderer::Renderer;
|
||||
pub use config::Config;
|
||||
/// The current version of `mdbook`.
|
||||
///
|
||||
/// This is provided as a way for custom preprocessors and renderers to do
|
||||
/// compatibility checks.
|
||||
pub const MDBOOK_VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
pub use crate::book::BookItem;
|
||||
pub use crate::book::MDBook;
|
||||
pub use crate::config::Config;
|
||||
pub use crate::renderer::Renderer;
|
||||
|
||||
/// The error types used through out this crate.
|
||||
pub mod errors {
|
||||
use std::path::PathBuf;
|
||||
|
||||
error_chain!{
|
||||
foreign_links {
|
||||
Io(::std::io::Error) #[doc = "A wrapper around `std::io::Error`"];
|
||||
HandlebarsRender(::handlebars::RenderError) #[doc = "Handlebars rendering failed"];
|
||||
HandlebarsTemplate(Box<::handlebars::TemplateError>) #[doc = "Unable to parse the template"];
|
||||
Utf8(::std::string::FromUtf8Error) #[doc = "Invalid UTF-8"];
|
||||
}
|
||||
|
||||
links {
|
||||
TomlQuery(::toml_query::error::Error, ::toml_query::error::ErrorKind) #[doc = "A TomlQuery error"];
|
||||
}
|
||||
|
||||
errors {
|
||||
/// A subprocess exited with an unsuccessful return code.
|
||||
Subprocess(message: String, output: ::std::process::Output) {
|
||||
description("A subprocess failed")
|
||||
display("{}: {}", message, String::from_utf8_lossy(&output.stdout))
|
||||
}
|
||||
|
||||
/// An error was encountered while parsing the `SUMMARY.md` file.
|
||||
ParseError(line: usize, col: usize, message: String) {
|
||||
description("A SUMMARY.md parsing error")
|
||||
display("Error at line {}, column {}: {}", line, col, message)
|
||||
}
|
||||
|
||||
/// The user tried to use a reserved filename.
|
||||
ReservedFilenameError(filename: PathBuf) {
|
||||
description("Reserved Filename")
|
||||
display("{} is reserved for internal use", filename.display())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Box to halve the size of Error
|
||||
impl From<::handlebars::TemplateError> for Error {
|
||||
fn from(e: ::handlebars::TemplateError) -> Error {
|
||||
From::from(Box::new(e))
|
||||
}
|
||||
}
|
||||
pub(crate) use anyhow::{bail, ensure, Context};
|
||||
pub use anyhow::{Error, Result};
|
||||
}
|
||||
|
||||
109
src/main.rs
Normal file
109
src/main.rs
Normal file
@@ -0,0 +1,109 @@
|
||||
#[macro_use]
|
||||
extern crate clap;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
use chrono::Local;
|
||||
use clap::{App, AppSettings, ArgMatches};
|
||||
use env_logger::Builder;
|
||||
use log::LevelFilter;
|
||||
use mdbook::utils;
|
||||
use std::env;
|
||||
use std::ffi::OsStr;
|
||||
use std::io::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
mod cmd;
|
||||
|
||||
const VERSION: &str = concat!("v", crate_version!());
|
||||
|
||||
fn main() {
|
||||
init_logger();
|
||||
|
||||
// Create a list of valid arguments and sub-commands
|
||||
let app = App::new(crate_name!())
|
||||
.about(crate_description!())
|
||||
.author("Mathieu David <mathieudavid@mathieudavid.org>")
|
||||
.version(VERSION)
|
||||
.setting(AppSettings::GlobalVersion)
|
||||
.setting(AppSettings::ArgRequiredElseHelp)
|
||||
.setting(AppSettings::ColoredHelp)
|
||||
.after_help(
|
||||
"For more information about a specific command, try `mdbook <command> --help`\n\
|
||||
The source code for mdBook is available at: https://github.com/rust-lang/mdBook",
|
||||
)
|
||||
.subcommand(cmd::init::make_subcommand())
|
||||
.subcommand(cmd::build::make_subcommand())
|
||||
.subcommand(cmd::test::make_subcommand())
|
||||
.subcommand(cmd::clean::make_subcommand());
|
||||
|
||||
#[cfg(feature = "watch")]
|
||||
let app = app.subcommand(cmd::watch::make_subcommand());
|
||||
#[cfg(feature = "serve")]
|
||||
let app = app.subcommand(cmd::serve::make_subcommand());
|
||||
|
||||
// Check which subcomamnd the user ran...
|
||||
let res = match app.get_matches().subcommand() {
|
||||
("init", Some(sub_matches)) => cmd::init::execute(sub_matches),
|
||||
("build", Some(sub_matches)) => cmd::build::execute(sub_matches),
|
||||
("clean", Some(sub_matches)) => cmd::clean::execute(sub_matches),
|
||||
#[cfg(feature = "watch")]
|
||||
("watch", Some(sub_matches)) => cmd::watch::execute(sub_matches),
|
||||
#[cfg(feature = "serve")]
|
||||
("serve", Some(sub_matches)) => cmd::serve::execute(sub_matches),
|
||||
("test", Some(sub_matches)) => cmd::test::execute(sub_matches),
|
||||
(_, _) => unreachable!(),
|
||||
};
|
||||
|
||||
if let Err(e) = res {
|
||||
utils::log_backtrace(&e);
|
||||
|
||||
std::process::exit(101);
|
||||
}
|
||||
}
|
||||
|
||||
fn init_logger() {
|
||||
let mut builder = Builder::new();
|
||||
|
||||
builder.format(|formatter, record| {
|
||||
writeln!(
|
||||
formatter,
|
||||
"{} [{}] ({}): {}",
|
||||
Local::now().format("%Y-%m-%d %H:%M:%S"),
|
||||
record.level(),
|
||||
record.target(),
|
||||
record.args()
|
||||
)
|
||||
});
|
||||
|
||||
if let Ok(var) = env::var("RUST_LOG") {
|
||||
builder.parse_filters(&var);
|
||||
} else {
|
||||
// if no RUST_LOG provided, default to logging at the Info level
|
||||
builder.filter(None, LevelFilter::Info);
|
||||
// Filter extraneous html5ever not-implemented messages
|
||||
builder.filter(Some("html5ever"), LevelFilter::Error);
|
||||
}
|
||||
|
||||
builder.init();
|
||||
}
|
||||
|
||||
fn get_book_dir(args: &ArgMatches) -> PathBuf {
|
||||
if let Some(dir) = args.value_of("dir") {
|
||||
// Check if path is relative from current dir, or absolute...
|
||||
let p = Path::new(dir);
|
||||
if p.is_relative() {
|
||||
env::current_dir().unwrap().join(dir)
|
||||
} else {
|
||||
p.to_path_buf()
|
||||
}
|
||||
} else {
|
||||
env::current_dir().expect("Unable to determine the current directory")
|
||||
}
|
||||
}
|
||||
|
||||
fn open<P: AsRef<OsStr>>(path: P) {
|
||||
if let Err(e) = open::that(path) {
|
||||
error!("Error opening web browser: {}", e);
|
||||
}
|
||||
}
|
||||
207
src/preprocess/cmd.rs
Normal file
207
src/preprocess/cmd.rs
Normal file
@@ -0,0 +1,207 @@
|
||||
use super::{Preprocessor, PreprocessorContext};
|
||||
use crate::book::Book;
|
||||
use crate::errors::*;
|
||||
use shlex::Shlex;
|
||||
use std::io::{self, Read, Write};
|
||||
use std::process::{Child, Command, Stdio};
|
||||
|
||||
/// A custom preprocessor which will shell out to a 3rd-party program.
|
||||
///
|
||||
/// # Preprocessing Protocol
|
||||
///
|
||||
/// When the `supports_renderer()` method is executed, `CmdPreprocessor` will
|
||||
/// execute the shell command `$cmd supports $renderer`. If the renderer is
|
||||
/// supported, custom preprocessors should exit with a exit code of `0`,
|
||||
/// any other exit code be considered as unsupported.
|
||||
///
|
||||
/// The `run()` method is implemented by passing a `(PreprocessorContext, Book)`
|
||||
/// tuple to the spawned command (`$cmd`) as JSON via `stdin`. Preprocessors
|
||||
/// should then "return" a processed book by printing it to `stdout` as JSON.
|
||||
/// For convenience, the `CmdPreprocessor::parse_input()` function can be used
|
||||
/// to parse the input provided by `mdbook`.
|
||||
///
|
||||
/// Exiting with a non-zero exit code while preprocessing is considered an
|
||||
/// error. `stderr` is passed directly through to the user, so it can be used
|
||||
/// for logging or emitting warnings if desired.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// An example preprocessor is available in this project's `examples/`
|
||||
/// directory.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct CmdPreprocessor {
|
||||
name: String,
|
||||
cmd: String,
|
||||
}
|
||||
|
||||
impl CmdPreprocessor {
|
||||
/// Create a new `CmdPreprocessor`.
|
||||
pub fn new(name: String, cmd: String) -> CmdPreprocessor {
|
||||
CmdPreprocessor { name, cmd }
|
||||
}
|
||||
|
||||
/// A convenience function custom preprocessors can use to parse the input
|
||||
/// written to `stdin` by a `CmdRenderer`.
|
||||
pub fn parse_input<R: Read>(reader: R) -> Result<(PreprocessorContext, Book)> {
|
||||
serde_json::from_reader(reader).with_context(|| "Unable to parse the input")
|
||||
}
|
||||
|
||||
fn write_input_to_child(&self, child: &mut Child, book: &Book, ctx: &PreprocessorContext) {
|
||||
let stdin = child.stdin.take().expect("Child has stdin");
|
||||
|
||||
if let Err(e) = self.write_input(stdin, &book, &ctx) {
|
||||
// Looks like the backend hung up before we could finish
|
||||
// sending it the render context. Log the error and keep going
|
||||
warn!("Error writing the RenderContext to the backend, {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
fn write_input<W: Write>(
|
||||
&self,
|
||||
writer: W,
|
||||
book: &Book,
|
||||
ctx: &PreprocessorContext,
|
||||
) -> Result<()> {
|
||||
serde_json::to_writer(writer, &(ctx, book)).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// The command this `Preprocessor` will invoke.
|
||||
pub fn cmd(&self) -> &str {
|
||||
&self.cmd
|
||||
}
|
||||
|
||||
fn command(&self) -> Result<Command> {
|
||||
let mut words = Shlex::new(&self.cmd);
|
||||
let executable = match words.next() {
|
||||
Some(e) => e,
|
||||
None => bail!("Command string was empty"),
|
||||
};
|
||||
|
||||
let mut cmd = Command::new(executable);
|
||||
|
||||
for arg in words {
|
||||
cmd.arg(arg);
|
||||
}
|
||||
|
||||
Ok(cmd)
|
||||
}
|
||||
}
|
||||
|
||||
impl Preprocessor for CmdPreprocessor {
|
||||
fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
fn run(&self, ctx: &PreprocessorContext, book: Book) -> Result<Book> {
|
||||
let mut cmd = self.command()?;
|
||||
|
||||
let mut child = cmd
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::inherit())
|
||||
.spawn()
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Unable to start the \"{}\" preprocessor. Is it installed?",
|
||||
self.name()
|
||||
)
|
||||
})?;
|
||||
|
||||
self.write_input_to_child(&mut child, &book, ctx);
|
||||
|
||||
let output = child.wait_with_output().with_context(|| {
|
||||
format!(
|
||||
"Error waiting for the \"{}\" preprocessor to complete",
|
||||
self.name
|
||||
)
|
||||
})?;
|
||||
|
||||
trace!("{} exited with output: {:?}", self.cmd, output);
|
||||
ensure!(
|
||||
output.status.success(),
|
||||
format!(
|
||||
"The \"{}\" preprocessor exited unsuccessfully with {} status",
|
||||
self.name, output.status
|
||||
)
|
||||
);
|
||||
|
||||
serde_json::from_slice(&output.stdout).with_context(|| {
|
||||
format!(
|
||||
"Unable to parse the preprocessed book from \"{}\" processor",
|
||||
self.name
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn supports_renderer(&self, renderer: &str) -> bool {
|
||||
debug!(
|
||||
"Checking if the \"{}\" preprocessor supports \"{}\"",
|
||||
self.name(),
|
||||
renderer
|
||||
);
|
||||
|
||||
let mut cmd = match self.command() {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
warn!(
|
||||
"Unable to create the command for the \"{}\" preprocessor, {}",
|
||||
self.name(),
|
||||
e
|
||||
);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
let outcome = cmd
|
||||
.arg("supports")
|
||||
.arg(renderer)
|
||||
.stdin(Stdio::null())
|
||||
.stdout(Stdio::inherit())
|
||||
.stderr(Stdio::inherit())
|
||||
.status()
|
||||
.map(|status| status.code() == Some(0));
|
||||
|
||||
if let Err(ref e) = outcome {
|
||||
if e.kind() == io::ErrorKind::NotFound {
|
||||
warn!(
|
||||
"The command wasn't found, is the \"{}\" preprocessor installed?",
|
||||
self.name
|
||||
);
|
||||
warn!("\tCommand: {}", self.cmd);
|
||||
}
|
||||
}
|
||||
|
||||
outcome.unwrap_or(false)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::MDBook;
|
||||
use std::path::Path;
|
||||
|
||||
fn guide() -> MDBook {
|
||||
let example = Path::new(env!("CARGO_MANIFEST_DIR")).join("guide");
|
||||
MDBook::load(example).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn round_trip_write_and_parse_input() {
|
||||
let cmd = CmdPreprocessor::new("test".to_string(), "test".to_string());
|
||||
let md = guide();
|
||||
let ctx = PreprocessorContext::new(
|
||||
md.root.clone(),
|
||||
md.config.clone(),
|
||||
"some-renderer".to_string(),
|
||||
);
|
||||
|
||||
let mut buffer = Vec::new();
|
||||
cmd.write_input(&mut buffer, &md.book, &ctx).unwrap();
|
||||
|
||||
let (got_ctx, got_book) = CmdPreprocessor::parse_input(buffer.as_slice()).unwrap();
|
||||
|
||||
assert_eq!(got_book, md.book);
|
||||
assert_eq!(got_ctx, ctx);
|
||||
}
|
||||
}
|
||||
105
src/preprocess/index.rs
Normal file
105
src/preprocess/index.rs
Normal file
@@ -0,0 +1,105 @@
|
||||
use regex::Regex;
|
||||
use std::path::Path;
|
||||
|
||||
use crate::errors::*;
|
||||
|
||||
use super::{Preprocessor, PreprocessorContext};
|
||||
use crate::book::{Book, BookItem};
|
||||
|
||||
/// A preprocessor for converting file name `README.md` to `index.md` since
|
||||
/// `README.md` is the de facto index file in markdown-based documentation.
|
||||
#[derive(Default)]
|
||||
pub struct IndexPreprocessor;
|
||||
|
||||
impl IndexPreprocessor {
|
||||
pub(crate) const NAME: &'static str = "index";
|
||||
|
||||
/// Create a new `IndexPreprocessor`.
|
||||
pub fn new() -> Self {
|
||||
IndexPreprocessor
|
||||
}
|
||||
}
|
||||
|
||||
impl Preprocessor for IndexPreprocessor {
|
||||
fn name(&self) -> &str {
|
||||
Self::NAME
|
||||
}
|
||||
|
||||
fn run(&self, ctx: &PreprocessorContext, mut book: Book) -> Result<Book> {
|
||||
let source_dir = ctx.root.join(&ctx.config.book.src);
|
||||
book.for_each_mut(|section: &mut BookItem| {
|
||||
if let BookItem::Chapter(ref mut ch) = *section {
|
||||
if let Some(ref mut path) = ch.path {
|
||||
if is_readme_file(&path) {
|
||||
let mut index_md = source_dir.join(path.with_file_name("index.md"));
|
||||
if index_md.exists() {
|
||||
warn_readme_name_conflict(&path, &&mut index_md);
|
||||
}
|
||||
|
||||
path.set_file_name("index.md");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(book)
|
||||
}
|
||||
}
|
||||
|
||||
fn warn_readme_name_conflict<P: AsRef<Path>>(readme_path: P, index_path: P) {
|
||||
let file_name = readme_path.as_ref().file_name().unwrap_or_default();
|
||||
let parent_dir = index_path
|
||||
.as_ref()
|
||||
.parent()
|
||||
.unwrap_or_else(|| index_path.as_ref());
|
||||
warn!(
|
||||
"It seems that there are both {:?} and index.md under \"{}\".",
|
||||
file_name,
|
||||
parent_dir.display()
|
||||
);
|
||||
warn!(
|
||||
"mdbook converts {:?} into index.html by default. It may cause",
|
||||
file_name
|
||||
);
|
||||
warn!("unexpected behavior if putting both files under the same directory.");
|
||||
warn!("To solve the warning, try to rearrange the book structure or disable");
|
||||
warn!("\"index\" preprocessor to stop the conversion.");
|
||||
}
|
||||
|
||||
fn is_readme_file<P: AsRef<Path>>(path: P) -> bool {
|
||||
lazy_static! {
|
||||
static ref RE: Regex = Regex::new(r"(?i)^readme$").unwrap();
|
||||
}
|
||||
RE.is_match(
|
||||
path.as_ref()
|
||||
.file_stem()
|
||||
.and_then(std::ffi::OsStr::to_str)
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn file_stem_exactly_matches_readme_case_insensitively() {
|
||||
let path = "path/to/Readme.md";
|
||||
assert!(is_readme_file(path));
|
||||
|
||||
let path = "path/to/README.md";
|
||||
assert!(is_readme_file(path));
|
||||
|
||||
let path = "path/to/rEaDmE.md";
|
||||
assert!(is_readme_file(path));
|
||||
|
||||
let path = "path/to/README.markdown";
|
||||
assert!(is_readme_file(path));
|
||||
|
||||
let path = "path/to/README";
|
||||
assert!(is_readme_file(path));
|
||||
|
||||
let path = "path/to/README-README.md";
|
||||
assert!(!is_readme_file(path));
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,32 +1,54 @@
|
||||
//! Book preprocessing.
|
||||
|
||||
pub use self::cmd::CmdPreprocessor;
|
||||
pub use self::index::IndexPreprocessor;
|
||||
pub use self::links::LinkPreprocessor;
|
||||
|
||||
mod cmd;
|
||||
mod index;
|
||||
mod links;
|
||||
|
||||
use book::Book;
|
||||
use config::Config;
|
||||
use errors::*;
|
||||
use crate::book::Book;
|
||||
use crate::config::Config;
|
||||
use crate::errors::*;
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// Extra information for a `Preprocessor` to give them more context when
|
||||
/// Extra information for a `Preprocessor` to give them more context when
|
||||
/// processing a book.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct PreprocessorContext {
|
||||
/// The location of the book directory on disk.
|
||||
pub root: PathBuf,
|
||||
/// The book configuration (`book.toml`).
|
||||
pub config: Config,
|
||||
/// The `Renderer` this preprocessor is being used with.
|
||||
pub renderer: String,
|
||||
/// The calling `mdbook` version.
|
||||
pub mdbook_version: String,
|
||||
#[serde(skip)]
|
||||
pub(crate) chapter_titles: RefCell<HashMap<PathBuf, String>>,
|
||||
#[serde(skip)]
|
||||
__non_exhaustive: (),
|
||||
}
|
||||
|
||||
impl PreprocessorContext {
|
||||
/// Create a new `PreprocessorContext`.
|
||||
pub(crate) fn new(root: PathBuf, config: Config) -> Self {
|
||||
PreprocessorContext { root, config }
|
||||
pub(crate) fn new(root: PathBuf, config: Config, renderer: String) -> Self {
|
||||
PreprocessorContext {
|
||||
root,
|
||||
config,
|
||||
renderer,
|
||||
mdbook_version: crate::MDBOOK_VERSION.to_string(),
|
||||
chapter_titles: RefCell::new(HashMap::new()),
|
||||
__non_exhaustive: (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An operation which is run immediately after loading a book into memory and
|
||||
/// An operation which is run immediately after loading a book into memory and
|
||||
/// before it gets rendered.
|
||||
pub trait Preprocessor {
|
||||
/// Get the `Preprocessor`'s name.
|
||||
@@ -34,5 +56,13 @@ pub trait Preprocessor {
|
||||
|
||||
/// Run this `Preprocessor`, allowing it to update the book before it is
|
||||
/// given to a renderer.
|
||||
fn run(&self, ctx: &PreprocessorContext, book: &mut Book) -> Result<()>;
|
||||
}
|
||||
fn run(&self, ctx: &PreprocessorContext, book: Book) -> Result<Book>;
|
||||
|
||||
/// A hint to `MDBook` whether this preprocessor is compatible with a
|
||||
/// particular renderer.
|
||||
///
|
||||
/// By default, always returns `true`.
|
||||
fn supports_renderer(&self, _renderer: &str) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,2 +1,3 @@
|
||||
pub mod navigation;
|
||||
pub mod theme;
|
||||
pub mod toc;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use std::path::Path;
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::Path;
|
||||
|
||||
use serde_json;
|
||||
use handlebars::{Context, Handlebars, Helper, RenderContext, RenderError, Renderable};
|
||||
use handlebars::{Context, Handlebars, Helper, Output, RenderContext, RenderError, Renderable};
|
||||
|
||||
use crate::utils;
|
||||
|
||||
type StringMap = BTreeMap<String, String>;
|
||||
|
||||
@@ -15,24 +15,25 @@ enum Target {
|
||||
|
||||
impl Target {
|
||||
/// Returns target if found.
|
||||
fn find(&self,
|
||||
base_path: &String,
|
||||
current_path: &String,
|
||||
current_item: &StringMap,
|
||||
previous_item: &StringMap,
|
||||
) -> Result<Option<StringMap>, RenderError> {
|
||||
match self {
|
||||
&Target::Next => {
|
||||
let previous_path = previous_item.get("path").ok_or_else(|| {
|
||||
RenderError::new("No path found for chapter in JSON data")
|
||||
})?;
|
||||
fn find(
|
||||
&self,
|
||||
base_path: &str,
|
||||
current_path: &str,
|
||||
current_item: &StringMap,
|
||||
previous_item: &StringMap,
|
||||
) -> Result<Option<StringMap>, RenderError> {
|
||||
match *self {
|
||||
Target::Next => {
|
||||
let previous_path = previous_item
|
||||
.get("path")
|
||||
.ok_or_else(|| RenderError::new("No path found for chapter in JSON data"))?;
|
||||
|
||||
if previous_path == base_path {
|
||||
return Ok(Some(current_item.clone()));
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
&Target::Previous => {
|
||||
Target::Previous => {
|
||||
if current_path == base_path {
|
||||
return Ok(Some(previous_item.clone()));
|
||||
}
|
||||
@@ -44,20 +45,43 @@ impl Target {
|
||||
}
|
||||
|
||||
fn find_chapter(
|
||||
rc: &mut RenderContext,
|
||||
target: Target
|
||||
ctx: &Context,
|
||||
rc: &mut RenderContext<'_, '_>,
|
||||
target: Target,
|
||||
) -> Result<Option<StringMap>, RenderError> {
|
||||
debug!("Get data from context");
|
||||
|
||||
let chapters = rc.evaluate_absolute("chapters").and_then(|c| {
|
||||
serde_json::value::from_value::<Vec<StringMap>>(c.clone())
|
||||
let chapters = rc.evaluate(ctx, "@root/chapters").and_then(|c| {
|
||||
serde_json::value::from_value::<Vec<StringMap>>(c.as_json().clone())
|
||||
.map_err(|_| RenderError::new("Could not decode the JSON data"))
|
||||
})?;
|
||||
|
||||
let base_path = rc.evaluate_absolute("path")?
|
||||
.as_str()
|
||||
.ok_or_else(|| RenderError::new("Type error for `path`, string expected"))?
|
||||
.replace("\"", "");
|
||||
let base_path = rc
|
||||
.evaluate(ctx, "@root/path")?
|
||||
.as_json()
|
||||
.as_str()
|
||||
.ok_or_else(|| RenderError::new("Type error for `path`, string expected"))?
|
||||
.replace("\"", "");
|
||||
|
||||
if !rc.evaluate(ctx, "@root/is_index")?.is_missing() {
|
||||
// Special case for index.md which may be a synthetic page.
|
||||
// Target::find won't match because there is no page with the path
|
||||
// "index.md" (unless there really is an index.md in SUMMARY.md).
|
||||
match target {
|
||||
Target::Previous => return Ok(None),
|
||||
Target::Next => match chapters
|
||||
.iter()
|
||||
.filter(|chapter| {
|
||||
// Skip things like "spacer"
|
||||
chapter.contains_key("path")
|
||||
})
|
||||
.nth(1)
|
||||
{
|
||||
Some(chapter) => return Ok(Some(chapter.clone())),
|
||||
None => return Ok(None),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
let mut previous: Option<StringMap> = None;
|
||||
|
||||
@@ -78,59 +102,88 @@ fn find_chapter(
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn render(
|
||||
_h: &Helper,
|
||||
r: &Handlebars,
|
||||
rc: &mut RenderContext,
|
||||
_h: &Helper<'_, '_>,
|
||||
r: &Handlebars<'_>,
|
||||
ctx: &Context,
|
||||
rc: &mut RenderContext<'_, '_>,
|
||||
out: &mut dyn Output,
|
||||
chapter: &StringMap,
|
||||
) -> Result<(), RenderError> {
|
||||
trace!("Creating BTreeMap to inject in context");
|
||||
|
||||
let mut context = BTreeMap::new();
|
||||
let base_path = rc
|
||||
.evaluate(ctx, "@root/path")?
|
||||
.as_json()
|
||||
.as_str()
|
||||
.ok_or_else(|| RenderError::new("Type error for `path`, string expected"))?
|
||||
.replace("\"", "");
|
||||
|
||||
chapter.get("name")
|
||||
.ok_or_else(|| RenderError::new("No title found for chapter in JSON data"))
|
||||
.map(|name| context.insert("title".to_owned(), json!(name)))?;
|
||||
context.insert(
|
||||
"path_to_root".to_owned(),
|
||||
json!(utils::fs::path_to_root(&base_path)),
|
||||
);
|
||||
|
||||
chapter.get("path")
|
||||
.ok_or_else(|| RenderError::new("No path found for chapter in JSON data"))
|
||||
.and_then(|p| {
|
||||
Path::new(p).with_extension("html")
|
||||
.to_str()
|
||||
.ok_or_else(|| RenderError::new("Link could not be converted to str"))
|
||||
.map(|p| context.insert("link".to_owned(), json!(p.replace("\\", "/"))))
|
||||
})?;
|
||||
chapter
|
||||
.get("name")
|
||||
.ok_or_else(|| RenderError::new("No title found for chapter in JSON data"))
|
||||
.map(|name| context.insert("title".to_owned(), json!(name)))?;
|
||||
|
||||
chapter
|
||||
.get("path")
|
||||
.ok_or_else(|| RenderError::new("No path found for chapter in JSON data"))
|
||||
.and_then(|p| {
|
||||
Path::new(p)
|
||||
.with_extension("html")
|
||||
.to_str()
|
||||
.ok_or_else(|| RenderError::new("Link could not be converted to str"))
|
||||
.map(|p| context.insert("link".to_owned(), json!(p.replace("\\", "/"))))
|
||||
})?;
|
||||
|
||||
trace!("Render template");
|
||||
|
||||
_h.template()
|
||||
.ok_or_else(|| RenderError::new("Error with the handlebars template"))
|
||||
.and_then(|t| {
|
||||
let mut local_rc = rc.with_context(Context::wraps(&context)?);
|
||||
t.render(r, &mut local_rc)
|
||||
let mut local_rc = rc.clone();
|
||||
let local_ctx = Context::wraps(&context)?;
|
||||
t.render(r, &local_ctx, &mut local_rc, out)
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn previous(_h: &Helper, r: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> {
|
||||
pub fn previous(
|
||||
_h: &Helper<'_, '_>,
|
||||
r: &Handlebars<'_>,
|
||||
ctx: &Context,
|
||||
rc: &mut RenderContext<'_, '_>,
|
||||
out: &mut dyn Output,
|
||||
) -> Result<(), RenderError> {
|
||||
trace!("previous (handlebars helper)");
|
||||
|
||||
if let Some(previous) = find_chapter(rc, Target::Previous)? {
|
||||
render(_h, r, rc, &previous)?;
|
||||
if let Some(previous) = find_chapter(ctx, rc, Target::Previous)? {
|
||||
render(_h, r, ctx, rc, out, &previous)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn next(_h: &Helper, r: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> {
|
||||
pub fn next(
|
||||
_h: &Helper<'_, '_>,
|
||||
r: &Handlebars<'_>,
|
||||
ctx: &Context,
|
||||
rc: &mut RenderContext<'_, '_>,
|
||||
out: &mut dyn Output,
|
||||
) -> Result<(), RenderError> {
|
||||
trace!("next (handlebars helper)");
|
||||
|
||||
if let Some(next) = find_chapter(rc, Target::Next)? {
|
||||
render(_h, r, rc, &next)?;
|
||||
if let Some(next) = find_chapter(ctx, rc, Target::Next)? {
|
||||
render(_h, r, ctx, rc, out, &next)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -138,97 +191,100 @@ pub fn next(_h: &Helper, r: &Handlebars, rc: &mut RenderContext) -> Result<(), R
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use super::*;
|
||||
|
||||
static TEMPLATE: &'static str =
|
||||
"{{#previous}}{{title}}: {{link}}{{/previous}}|{{#next}}{{title}}: {{link}}{{/next}}";
|
||||
static TEMPLATE: &str =
|
||||
"{{#previous}}{{title}}: {{link}}{{/previous}}|{{#next}}{{title}}: {{link}}{{/next}}";
|
||||
|
||||
#[test]
|
||||
fn test_next_previous() {
|
||||
let data = json!({
|
||||
"name": "two",
|
||||
"path": "two.path",
|
||||
"chapters": [
|
||||
{
|
||||
"name": "one",
|
||||
"path": "one.path"
|
||||
},
|
||||
{
|
||||
"name": "two",
|
||||
"path": "two.path",
|
||||
},
|
||||
{
|
||||
"name": "three",
|
||||
"path": "three.path"
|
||||
}
|
||||
]
|
||||
});
|
||||
#[test]
|
||||
fn test_next_previous() {
|
||||
let data = json!({
|
||||
"name": "two",
|
||||
"path": "two.path",
|
||||
"chapters": [
|
||||
{
|
||||
"name": "one",
|
||||
"path": "one.path"
|
||||
},
|
||||
{
|
||||
"name": "two",
|
||||
"path": "two.path",
|
||||
},
|
||||
{
|
||||
"name": "three",
|
||||
"path": "three.path"
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
let mut h = Handlebars::new();
|
||||
h.register_helper("previous", Box::new(previous));
|
||||
h.register_helper("next", Box::new(next));
|
||||
let mut h = Handlebars::new();
|
||||
h.register_helper("previous", Box::new(previous));
|
||||
h.register_helper("next", Box::new(next));
|
||||
|
||||
assert_eq!(
|
||||
h.template_render(TEMPLATE, &data).unwrap(),
|
||||
"one: one.html|three: three.html");
|
||||
}
|
||||
assert_eq!(
|
||||
h.render_template(TEMPLATE, &data).unwrap(),
|
||||
"one: one.html|three: three.html"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_first() {
|
||||
let data = json!({
|
||||
"name": "one",
|
||||
"path": "one.path",
|
||||
"chapters": [
|
||||
{
|
||||
"name": "one",
|
||||
"path": "one.path"
|
||||
},
|
||||
{
|
||||
"name": "two",
|
||||
"path": "two.path",
|
||||
},
|
||||
{
|
||||
"name": "three",
|
||||
"path": "three.path"
|
||||
}
|
||||
]
|
||||
});
|
||||
#[test]
|
||||
fn test_first() {
|
||||
let data = json!({
|
||||
"name": "one",
|
||||
"path": "one.path",
|
||||
"chapters": [
|
||||
{
|
||||
"name": "one",
|
||||
"path": "one.path"
|
||||
},
|
||||
{
|
||||
"name": "two",
|
||||
"path": "two.path",
|
||||
},
|
||||
{
|
||||
"name": "three",
|
||||
"path": "three.path"
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
let mut h = Handlebars::new();
|
||||
h.register_helper("previous", Box::new(previous));
|
||||
h.register_helper("next", Box::new(next));
|
||||
let mut h = Handlebars::new();
|
||||
h.register_helper("previous", Box::new(previous));
|
||||
h.register_helper("next", Box::new(next));
|
||||
|
||||
assert_eq!(
|
||||
h.template_render(TEMPLATE, &data).unwrap(),
|
||||
"|two: two.html");
|
||||
}
|
||||
#[test]
|
||||
fn test_last() {
|
||||
let data = json!({
|
||||
"name": "three",
|
||||
"path": "three.path",
|
||||
"chapters": [
|
||||
{
|
||||
"name": "one",
|
||||
"path": "one.path"
|
||||
},
|
||||
{
|
||||
"name": "two",
|
||||
"path": "two.path",
|
||||
},
|
||||
{
|
||||
"name": "three",
|
||||
"path": "three.path"
|
||||
}
|
||||
]
|
||||
});
|
||||
assert_eq!(
|
||||
h.render_template(TEMPLATE, &data).unwrap(),
|
||||
"|two: two.html"
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn test_last() {
|
||||
let data = json!({
|
||||
"name": "three",
|
||||
"path": "three.path",
|
||||
"chapters": [
|
||||
{
|
||||
"name": "one",
|
||||
"path": "one.path"
|
||||
},
|
||||
{
|
||||
"name": "two",
|
||||
"path": "two.path",
|
||||
},
|
||||
{
|
||||
"name": "three",
|
||||
"path": "three.path"
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
let mut h = Handlebars::new();
|
||||
h.register_helper("previous", Box::new(previous));
|
||||
h.register_helper("next", Box::new(next));
|
||||
let mut h = Handlebars::new();
|
||||
h.register_helper("previous", Box::new(previous));
|
||||
h.register_helper("next", Box::new(next));
|
||||
|
||||
assert_eq!(
|
||||
h.template_render(TEMPLATE, &data).unwrap(),
|
||||
"two: two.html|");
|
||||
}
|
||||
assert_eq!(
|
||||
h.render_template(TEMPLATE, &data).unwrap(),
|
||||
"two: two.html|"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user