1 | use regex::Regex; |
2 | use std::path::Path; |
3 | |
4 | use super::{Preprocessor, PreprocessorContext}; |
5 | use crate::book::{Book, BookItem}; |
6 | use crate::errors::*; |
7 | use log::warn; |
8 | use once_cell::sync::Lazy; |
9 | |
10 | /// A preprocessor for converting file name `README.md` to `index.md` since |
11 | /// `README.md` is the de facto index file in markdown-based documentation. |
12 | #[derive (Default)] |
13 | pub struct IndexPreprocessor; |
14 | |
15 | impl IndexPreprocessor { |
16 | pub(crate) const NAME: &'static str = "index" ; |
17 | |
18 | /// Create a new `IndexPreprocessor`. |
19 | pub fn new() -> Self { |
20 | IndexPreprocessor |
21 | } |
22 | } |
23 | |
24 | impl Preprocessor for IndexPreprocessor { |
25 | fn name(&self) -> &str { |
26 | Self::NAME |
27 | } |
28 | |
29 | fn run(&self, ctx: &PreprocessorContext, mut book: Book) -> Result<Book> { |
30 | let source_dir = ctx.root.join(&ctx.config.book.src); |
31 | book.for_each_mut(|section: &mut BookItem| { |
32 | if let BookItem::Chapter(ref mut ch) = *section { |
33 | if let Some(ref mut path) = ch.path { |
34 | if is_readme_file(&path) { |
35 | let mut index_md = source_dir.join(path.with_file_name("index.md" )); |
36 | if index_md.exists() { |
37 | warn_readme_name_conflict(&path, &&mut index_md); |
38 | } |
39 | |
40 | path.set_file_name("index.md" ); |
41 | } |
42 | } |
43 | } |
44 | }); |
45 | |
46 | Ok(book) |
47 | } |
48 | } |
49 | |
50 | fn warn_readme_name_conflict<P: AsRef<Path>>(readme_path: P, index_path: P) { |
51 | let file_name: &OsStr = readme_path.as_ref().file_name().unwrap_or_default(); |
52 | let parent_dir: &Path = index_pathOption<&Path> |
53 | .as_ref() |
54 | .parent() |
55 | .unwrap_or_else(|| index_path.as_ref()); |
56 | warn!( |
57 | "It seems that there are both {:?} and index.md under \"{}\"." , |
58 | file_name, |
59 | parent_dir.display() |
60 | ); |
61 | warn!( |
62 | "mdbook converts {:?} into index.html by default. It may cause" , |
63 | file_name |
64 | ); |
65 | warn!("unexpected behavior if putting both files under the same directory." ); |
66 | warn!("To solve the warning, try to rearrange the book structure or disable" ); |
67 | warn!(" \"index \" preprocessor to stop the conversion." ); |
68 | } |
69 | |
70 | fn is_readme_file<P: AsRef<Path>>(path: P) -> bool { |
71 | static RE: Lazy<Regex> = Lazy::new(|| Regex::new(re:r"(?i)^readme$" ).unwrap()); |
72 | |
73 | RE.is_match( |
74 | text:path.as_ref() |
75 | .file_stem() |
76 | .and_then(std::ffi::OsStr::to_str) |
77 | .unwrap_or_default(), |
78 | ) |
79 | } |
80 | |
81 | #[cfg (test)] |
82 | mod tests { |
83 | use super::*; |
84 | |
85 | #[test ] |
86 | fn file_stem_exactly_matches_readme_case_insensitively() { |
87 | let path = "path/to/Readme.md" ; |
88 | assert!(is_readme_file(path)); |
89 | |
90 | let path = "path/to/README.md" ; |
91 | assert!(is_readme_file(path)); |
92 | |
93 | let path = "path/to/rEaDmE.md" ; |
94 | assert!(is_readme_file(path)); |
95 | |
96 | let path = "path/to/README.markdown" ; |
97 | assert!(is_readme_file(path)); |
98 | |
99 | let path = "path/to/README" ; |
100 | assert!(is_readme_file(path)); |
101 | |
102 | let path = "path/to/README-README.md" ; |
103 | assert!(!is_readme_file(path)); |
104 | } |
105 | } |
106 | |