Skip to content

Commit 6959964

Browse files
weihangloMichael-F-Bryan
authored andcommitted
Add index preprocessor (#685)
* Add index preprocessor README.md is a de facto index file in markdown-based documentation. Hence, we respect to README.md and convert it into index.html. * Fix warning for unused variables * Update tests for config * Match file stem case-insensitively for IndexPreprocessor * Add tests for IndexPreprocessor * Update book example to fit index preprocessor
1 parent 69fef40 commit 6959964

File tree

15 files changed

+162
-13
lines changed

15 files changed

+162
-13
lines changed

book-example/src/SUMMARY.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,26 @@
11
# Summary
22

33
- [mdBook](README.md)
4-
- [Command Line Tool](cli/cli-tool.md)
4+
- [Command Line Tool](cli/README.md)
55
- [init](cli/init.md)
66
- [build](cli/build.md)
77
- [watch](cli/watch.md)
88
- [serve](cli/serve.md)
99
- [test](cli/test.md)
1010
- [clean](cli/clean.md)
11-
- [Format](format/format.md)
11+
- [Format](format/README.md)
1212
- [SUMMARY.md](format/summary.md)
1313
- [Configuration](format/config.md)
14-
- [Theme](format/theme/theme.md)
14+
- [Theme](format/theme/README.md)
1515
- [index.hbs](format/theme/index-hbs.md)
1616
- [Syntax highlighting](format/theme/syntax-highlighting.md)
1717
- [Editor](format/theme/editor.md)
1818
- [MathJax Support](format/mathjax.md)
1919
- [mdBook specific features](format/mdbook.md)
20-
- [For Developers](for_developers/index.md)
20+
- [For Developers](for_developers/README.md)
2121
- [Preprocessors](for_developers/preprocessors.md)
2222
- [Alternate Backends](for_developers/backends.md)
23+
2324
-----------
25+
2426
[Contributors](misc/contributors.md)
File renamed without changes.
File renamed without changes.

book-example/src/misc/contributors.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@ If you have contributed to mdBook and I forgot to add you, don't hesitate to add
1616
- [projektir](https://github.com/projektir)
1717
- [Phaiax](https://github.com/Phaiax)
1818
- [Matt Ickstadt](https://github.com/mattico)
19+
- Weihang Lo ([@weihanglo](https://github.com/weihanglo))

src/book/mod.rs

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,12 @@ use toml::Value;
2121

2222
use utils;
2323
use renderer::{CmdRenderer, HtmlHandlebars, RenderContext, Renderer};
24-
use preprocess::{LinkPreprocessor, Preprocessor, PreprocessorContext};
24+
use preprocess::{
25+
LinkPreprocessor,
26+
IndexPreprocessor,
27+
Preprocessor,
28+
PreprocessorContext
29+
};
2530
use errors::*;
2631

2732
use config::Config;
@@ -218,6 +223,7 @@ impl MDBook {
218223
let preprocess_context = PreprocessorContext::new(self.root.clone(), self.config.clone());
219224

220225
LinkPreprocessor::new().run(&preprocess_context, &mut self.book)?;
226+
IndexPreprocessor::new().run(&preprocess_context, &mut self.book)?;
221227

222228
for item in self.iter() {
223229
if let BookItem::Chapter(ref ch) = *item {
@@ -322,15 +328,19 @@ fn determine_renderers(config: &Config) -> Vec<Box<Renderer>> {
322328
}
323329

324330
fn default_preprocessors() -> Vec<Box<Preprocessor>> {
325-
vec![Box::new(LinkPreprocessor::new())]
331+
vec![
332+
Box::new(LinkPreprocessor::new()),
333+
Box::new(IndexPreprocessor::new()),
334+
]
326335
}
327336

328337
/// Look at the `MDBook` and try to figure out what preprocessors to run.
329338
fn determine_preprocessors(config: &Config) -> Result<Vec<Box<Preprocessor>>> {
330339
let preprocess_list = match config.build.preprocess {
331340
Some(ref p) => p,
332-
// If no preprocessor field is set, default to the LinkPreprocessor. This allows you
333-
// to disable the LinkPreprocessor by setting "preprocess" to an empty list.
341+
// If no preprocessor field is set, default to the LinkPreprocessor and
342+
// IndexPreprocessor. This allows you to disable default preprocessors
343+
// by setting "preprocess" to an empty list.
334344
None => return Ok(default_preprocessors()),
335345
};
336346

@@ -339,6 +349,7 @@ fn determine_preprocessors(config: &Config) -> Result<Vec<Box<Preprocessor>>> {
339349
for key in preprocess_list {
340350
match key.as_ref() {
341351
"links" => preprocessors.push(Box::new(LinkPreprocessor::new())),
352+
"index" => preprocessors.push(Box::new(IndexPreprocessor::new())),
342353
_ => bail!("{:?} is not a recognised preprocessor", key),
343354
}
344355
}
@@ -403,7 +414,7 @@ mod tests {
403414
}
404415

405416
#[test]
406-
fn config_defaults_to_link_preprocessor_if_not_set() {
417+
fn config_defaults_to_link_and_index_preprocessor_if_not_set() {
407418
let cfg = Config::default();
408419

409420
// make sure we haven't got anything in the `output` table
@@ -412,8 +423,9 @@ mod tests {
412423
let got = determine_preprocessors(&cfg);
413424

414425
assert!(got.is_ok());
415-
assert_eq!(got.as_ref().unwrap().len(), 1);
426+
assert_eq!(got.as_ref().unwrap().len(), 2);
416427
assert_eq!(got.as_ref().unwrap()[0].name(), "links");
428+
assert_eq!(got.as_ref().unwrap()[1].name(), "index");
417429
}
418430

419431
#[test]

src/preprocess/index.rs

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

src/preprocess/mod.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
//! Book preprocessing.
22
33
pub use self::links::LinkPreprocessor;
4+
pub use self::index::IndexPreprocessor;
45

56
mod links;
7+
mod index;
68

79
use book::Book;
810
use config::Config;
911
use errors::*;
1012

1113
use std::path::PathBuf;
1214

13-
/// Extra information for a `Preprocessor` to give them more context when
15+
/// Extra information for a `Preprocessor` to give them more context when
1416
/// processing a book.
1517
pub struct PreprocessorContext {
1618
/// The location of the book directory on disk.
@@ -26,7 +28,7 @@ impl PreprocessorContext {
2628
}
2729
}
2830

29-
/// An operation which is run immediately after loading a book into memory and
31+
/// An operation which is run immediately after loading a book into memory and
3032
/// before it gets rendered.
3133
pub trait Preprocessor {
3234
/// Get the `Preprocessor`'s name.
@@ -35,4 +37,4 @@ pub trait Preprocessor {
3537
/// Run this `Preprocessor`, allowing it to update the book before it is
3638
/// given to a renderer.
3739
fn run(&self, ctx: &PreprocessorContext, book: &mut Book) -> Result<()>;
38-
}
40+
}

tests/dummy_book/src2/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Root README

0 commit comments

Comments
 (0)