Skip to content

Commit 0e68891

Browse files
committed
Implement support for .mdbookignore files
1 parent 05fbc5d commit 0e68891

16 files changed

Lines changed: 104 additions & 15 deletions

File tree

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,17 +89,17 @@ rust-version.workspace = true
8989

9090
[dependencies]
9191
anyhow.workspace = true
92-
clap.workspace = true
9392
clap_complete.workspace = true
93+
clap.workspace = true
94+
ignore.workspace = true
9495
mdbook-core.workspace = true
9596
mdbook-driver.workspace = true
9697
mdbook-html.workspace = true
9798
opener.workspace = true
98-
tracing.workspace = true
9999
tracing-subscriber.workspace = true
100+
tracing.workspace = true
100101

101102
# Watch feature
102-
ignore = { workspace = true, optional = true }
103103
notify = { workspace = true, optional = true }
104104
notify-debouncer-mini = { workspace = true, optional = true }
105105
pathdiff = { workspace = true, optional = true }
@@ -125,7 +125,7 @@ walkdir.workspace = true
125125

126126
[features]
127127
default = ["watch", "serve", "search"]
128-
watch = ["dep:notify", "dep:notify-debouncer-mini", "dep:ignore", "dep:pathdiff", "dep:walkdir"]
128+
watch = ["dep:notify", "dep:notify-debouncer-mini", "dep:pathdiff", "dep:walkdir"]
129129
serve = ["dep:futures-util", "dep:tokio", "dep:axum", "dep:tower-http"]
130130
search = ["mdbook-html/search"]
131131

crates/mdbook-core/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ rust-version.workspace = true
99

1010
[dependencies]
1111
anyhow.workspace = true
12+
ignore.workspace = true
1213
regex.workspace = true
1314
serde.workspace = true
1415
serde_json.workspace = true

crates/mdbook-core/src/utils/fs.rs

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! Filesystem utilities and helpers.
22
33
use anyhow::{Context, Result};
4+
use ignore::gitignore::{Gitignore, GitignoreBuilder};
45
use std::fs;
56
use std::path::{Component, Path, PathBuf};
67
use tracing::debug;
@@ -96,12 +97,30 @@ pub fn copy_files_except_ext(
9697
recursive: bool,
9798
avoid_dir: Option<&PathBuf>,
9899
ext_blacklist: &[&str],
100+
) -> Result<()> {
101+
let mut builder = GitignoreBuilder::new(from);
102+
for ext in ext_blacklist {
103+
builder.add_line(None, &format!("*.{ext}"))?;
104+
}
105+
let ignore = builder.build()?;
106+
107+
copy_files_except_ignored(from, to, recursive, avoid_dir, Some(&ignore))
108+
}
109+
110+
/// Copies all files of a directory to another one except the files that are
111+
/// ignored by the passed [`Gitignore`]
112+
pub fn copy_files_except_ignored(
113+
from: &Path,
114+
to: &Path,
115+
recursive: bool,
116+
avoid_dir: Option<&PathBuf>,
117+
ignore: Option<&Gitignore>,
99118
) -> Result<()> {
100119
debug!(
101120
"Copying all files from {} to {} (blacklist: {:?}), avoiding {:?}",
102121
from.display(),
103122
to.display(),
104-
ext_blacklist,
123+
ignore,
105124
avoid_dir
106125
);
107126

@@ -119,6 +138,14 @@ pub fn copy_files_except_ext(
119138
let entry_file_name = entry.file_name().unwrap();
120139
let target_file_path = to.join(entry_file_name);
121140

141+
// Check if it is in the blacklist
142+
if let Some(ignore) = ignore {
143+
let path = entry.as_path();
144+
if ignore.matched(path, path.is_dir()).is_ignore() {
145+
continue;
146+
}
147+
}
148+
122149
// If the entry is a dir and the recursive option is enabled, call itself
123150
if metadata.is_dir() && recursive {
124151
if entry == to.as_os_str() {
@@ -131,19 +158,20 @@ pub fn copy_files_except_ext(
131158
}
132159
}
133160

161+
if let Some(ignore) = ignore {
162+
let path = entry.as_path();
163+
if ignore.matched(path, path.is_dir()).is_ignore() {
164+
continue;
165+
}
166+
}
167+
134168
// check if output dir already exists
135169
if !target_file_path.exists() {
136170
fs::create_dir(&target_file_path)?;
137171
}
138172

139-
copy_files_except_ext(&entry, &target_file_path, true, avoid_dir, ext_blacklist)?;
173+
copy_files_except_ignored(&entry, &target_file_path, true, avoid_dir, ignore)?;
140174
} else if metadata.is_file() {
141-
// Check if it is in the blacklist
142-
if let Some(ext) = entry.extension() {
143-
if ext_blacklist.contains(&ext.to_str().unwrap()) {
144-
continue;
145-
}
146-
}
147175
debug!("Copying {entry:?} to {target_file_path:?}");
148176
copy(&entry, &target_file_path)?;
149177
}
@@ -247,7 +275,7 @@ mod tests {
247275
if let Err(e) =
248276
copy_files_except_ext(tmp.path(), &tmp.path().join("output"), true, None, &["md"])
249277
{
250-
panic!("Error while executing the function:\n{e:?}");
278+
panic!("Error while executing the function:\n{:?}", e);
251279
}
252280

253281
// Check if the correct files where created

crates/mdbook-html/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ font-awesome-as-a-crate.workspace = true
1515
handlebars.workspace = true
1616
hex.workspace = true
1717
html5ever.workspace = true
18+
ignore.workspace = true
1819
indexmap.workspace = true
1920
mdbook-core.workspace = true
2021
mdbook-markdown.workspace = true

crates/mdbook-html/src/html_handlebars/hbs_renderer.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use crate::theme::Theme;
66
use crate::utils::ToUrlPath;
77
use anyhow::{Context, Result, bail};
88
use handlebars::Handlebars;
9+
use ignore::gitignore::GitignoreBuilder;
910
use mdbook_core::book::{Book, BookItem, Chapter};
1011
use mdbook_core::config::{BookConfig, Config, HtmlConfig};
1112
use mdbook_core::utils::fs;
@@ -444,7 +445,24 @@ impl Renderer for HtmlHandlebars {
444445
.context("Unable to emit redirects")?;
445446

446447
// Copy all remaining files, avoid a recursive copy from/to the book build dir
447-
fs::copy_files_except_ext(&src_dir, destination, true, Some(&build_dir), &["md"])?;
448+
let mut builder = GitignoreBuilder::new(&src_dir);
449+
let mdbook_ignore = src_dir.join(".mdbookignore");
450+
println!("mdbook_ignore={}", mdbook_ignore.display());
451+
if mdbook_ignore.exists() {
452+
if let Some(err) = builder.add(mdbook_ignore) {
453+
warn!("Unable to load '.mdbookignore' file: {}", err);
454+
}
455+
}
456+
builder.add_line(None, "*.md")?;
457+
let ignore = builder.build()?;
458+
459+
fs::copy_files_except_ignored(
460+
&src_dir,
461+
destination,
462+
true,
463+
Some(&build_dir),
464+
Some(&ignore),
465+
)?;
448466

449467
info!("HTML book written to `{}`", destination.display());
450468

guide/src/format/configuration/renderers.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,19 @@ This will generate an HTML page which will automatically redirect to the given l
322322

323323
When fragment redirects are specified, the page must use JavaScript to redirect to the correct location. This is useful if you rename or move a section header. Fragment redirects work with existing pages and deleted pages.
324324

325-
## Markdown renderer
325+
### `.mdbookignore`
326+
327+
You can use a `.mdbookignore` file to exclude files from the build process.
328+
The file is placed in the `src` directory of your book and has the same format as
329+
[`.gitignore`](https://git-scm.com/docs/gitignore) files.
330+
331+
For example:
332+
```
333+
*.rs
334+
/target/
335+
```
336+
337+
## Markdown Renderer
326338

327339
The Markdown renderer will run preprocessors and then output the resulting
328340
Markdown. This is mostly useful for debugging preprocessors, especially in

tests/testsuite/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ mod includes;
1212
mod index;
1313
mod init;
1414
mod markdown;
15+
mod mdbookignore;
1516
mod playground;
1617
mod preprocessor;
1718
mod print;

tests/testsuite/mdbookignore.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
use crate::prelude::BookTest;
2+
3+
// Simple smoke test that mdbookignore works.
4+
#[test]
5+
fn ignore_file_is_respected() {
6+
let mut test = BookTest::from_dir("mdbookignore/simple");
7+
test.run("build", |_| ());
8+
9+
assert!(test.dir.join("book/index.html").exists());
10+
assert!(test.dir.join("book/normal_file").exists());
11+
assert!(!test.dir.join("book/ignored_file").exists());
12+
assert!(!test.dir.join("book/.mdbookignore").exists());
13+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Basic book
2+
3+
This GUI test book is the default book with a single chapter.

0 commit comments

Comments
 (0)