]>
git.proxmox.com Git - rustc.git/blob - vendor/mdbook/src/cmd/watch.rs
1 use super::command_prelude
::*;
2 use crate::{get_book_dir, open}
;
3 use mdbook
::errors
::Result
;
6 use std
::path
::{Path, PathBuf}
;
7 use std
::sync
::mpsc
::channel
;
8 use std
::thread
::sleep
;
9 use std
::time
::Duration
;
11 // Create clap subcommand arguments
12 pub fn make_subcommand() -> Command
{
14 .about("Watches a book's files and rebuilds it on changes")
20 // Watch command implementation
21 pub fn execute(args
: &ArgMatches
) -> Result
<()> {
22 let book_dir
= get_book_dir(args
);
23 let mut book
= MDBook
::load(&book_dir
)?
;
25 let update_config
= |book
: &mut MDBook
| {
26 if let Some(dest_dir
) = args
.get_one
::<PathBuf
>("dest-dir") {
27 book
.config
.build
.build_dir
= dest_dir
.into();
30 update_config(&mut book
);
32 if args
.get_flag("open") {
34 let path
= book
.build_dir_for("html").join("index.html");
36 error
!("No chapter available to open");
42 trigger_on_change(&book
, |paths
, book_dir
| {
43 info
!("Files changed: {:?}\nBuilding book...\n", paths
);
44 let result
= MDBook
::load(&book_dir
).and_then(|mut b
| {
45 update_config(&mut b
);
49 if let Err(e
) = result
{
50 error
!("Unable to build the book");
51 utils
::log_backtrace(&e
);
58 fn remove_ignored_files(book_root
: &Path
, paths
: &[PathBuf
]) -> Vec
<PathBuf
> {
63 match find_gitignore(book_root
) {
64 Some(gitignore_path
) => {
65 match gitignore
::File
::new(gitignore_path
.as_path()) {
66 Ok(exclusion_checker
) => filter_ignored_files(exclusion_checker
, paths
),
68 // We're unable to read the .gitignore file, so we'll silently allow everything.
69 // Please see discussion: https://github.com/rust-lang/mdBook/pull/1051
70 paths
.iter().map(|path
| path
.to_path_buf()).collect()
75 // There is no .gitignore file.
76 paths
.iter().map(|path
| path
.to_path_buf()).collect()
81 fn find_gitignore(book_root
: &Path
) -> Option
<PathBuf
> {
84 .map(|p
| p
.join(".gitignore"))
88 fn filter_ignored_files(exclusion_checker
: gitignore
::File
, paths
: &[PathBuf
]) -> Vec
<PathBuf
> {
91 .filter(|path
| match exclusion_checker
.is_excluded(path
) {
92 Ok(exclude
) => !exclude
,
95 "Unable to determine if {:?} is excluded: {:?}. Including it.",
101 .map(|path
| path
.to_path_buf())
105 /// Calls the closure when a book source file is changed, blocking indefinitely.
106 pub fn trigger_on_change
<F
>(book
: &MDBook
, closure
: F
)
108 F
: Fn(Vec
<PathBuf
>, &Path
),
110 use notify
::RecursiveMode
::*;
112 // Create a channel to receive the events.
113 let (tx
, rx
) = channel();
115 let mut debouncer
= match notify_debouncer_mini
::new_debouncer(Duration
::from_secs(1), None
, tx
)
119 error
!("Error while trying to watch the files:\n\n\t{:?}", e
);
120 std
::process
::exit(1)
123 let watcher
= debouncer
.watcher();
125 // Add the source directory to the watcher
126 if let Err(e
) = watcher
.watch(&book
.source_dir(), Recursive
) {
127 error
!("Error while watching {:?}:\n {:?}", book
.source_dir(), e
);
128 std
::process
::exit(1);
131 let _
= watcher
.watch(&book
.theme_dir(), Recursive
);
133 // Add the book.toml file to the watcher if it exists
134 let _
= watcher
.watch(&book
.root
.join("book.toml"), NonRecursive
);
136 for dir
in &book
.config
.build
.extra_watch_dirs
{
137 let path
= dir
.canonicalize().unwrap();
138 if let Err(e
) = watcher
.watch(&path
, Recursive
) {
140 "Error while watching extra directory {:?}:\n {:?}",
143 std
::process
::exit(1);
147 info
!("Listening for changes...");
150 let first_event
= rx
.recv().unwrap();
151 sleep(Duration
::from_millis(50));
152 let other_events
= rx
.try_iter();
154 let all_events
= std
::iter
::once(first_event
).chain(other_events
);
156 let paths
: Vec
<_
> = all_events
157 .filter_map(|event
| match event
{
158 Ok(events
) => Some(events
),
160 for error
in errors
{
161 log
::warn
!("error while watching for changes: {error}");
167 .map(|event
| event
.path
)
170 // If we are watching files outside the current repository (via extra-watch-dirs), then they are definitionally
171 // ignored by gitignore. So we handle this case by including such files into the watched paths list.
172 let any_external_paths
= paths
.iter().filter(|p
| !p
.starts_with(&book
.root
)).cloned();
173 let mut paths
= remove_ignored_files(&book
.root
, &paths
[..]);
174 paths
.extend(any_external_paths
);
176 if !paths
.is_empty() {
177 closure(paths
, &book
.root
);