mdbook_incremental_preview/
lib.rs1use std::{
2 borrow::Cow,
3 cell::RefCell,
4 collections::{HashMap, HashSet},
5 ffi::OsStr,
6 io, iter, mem,
7 net::SocketAddr,
8 path::{Path, PathBuf},
9 sync::{Arc, Mutex, RwLock},
10 time::Duration,
11};
12
13use anyhow::{bail, Context};
14use drop_this::*;
15use futures_util::sink::SinkExt;
16use handlebars::Handlebars;
17use ignore::gitignore::Gitignore;
18use mdbook::{
19 book::{preprocessor_should_run, Book, Chapter},
20 config::{Code, HtmlConfig, Playground, RustEdition},
21 errors::*,
22 preprocess::{Preprocessor, PreprocessorContext},
23 renderer::{
24 html_handlebars::hbs_renderer::{make_data, RenderItemContext},
25 HtmlHandlebars, RenderContext,
26 },
27 theme::{self, playground_editor, Theme},
28 utils, BookItem, Config, MDBook, Renderer, MDBOOK_VERSION,
29};
30use notify::{RecommendedWatcher, RecursiveMode::*};
31use notify_debouncer_mini::{DebounceEventHandler, DebouncedEvent, Debouncer};
32use serde_json::json;
33use tempfile::{tempdir, TempDir};
34use tokio::{
35 fs::{self, File},
36 io::AsyncReadExt,
37 select, spawn,
38 sync::{mpsc, oneshot, watch},
39 task::{block_in_place, spawn_blocking, yield_now, JoinHandle},
40 time::timeout,
41};
42use tokio_gen_server::prelude::*;
43use tokio_two_join_set::TwoJoinSet;
44use tracing::*;
45use warp::{
46 filters::{
47 path::{FullPath, Tail},
48 ws::{WebSocket, Ws},
49 BoxedFilter,
50 },
51 reply::{with_header, WithHeader},
52 ws::Message,
53 Filter,
54};
55
56pub mod build_book;
57pub mod git_ignore;
58pub mod patch_registry;
59pub mod previewing;
60pub mod rebuilding;
61pub mod rendering;
62pub mod watch_files;
63pub mod web_server;
64
65use build_book::*;
66use git_ignore::*;
67use patch_registry::*;
68use previewing::*;
69use rebuilding::*;
70use rendering::*;
71use watch_files::*;
72use web_server::*;
73
74const LIVE_PATCH_WEBSOCKET_PATH: &str = "__mdbook_incremental_preview_live_patch";
79
80pub async fn preview_continuously(
83 book_root: PathBuf,
84 socket_address: SocketAddr,
85 open_browser: bool,
86) -> Result<()> {
87 let previewer = Previewer::try_new()?;
88 let (handle, actor_ref) = previewer.spawn();
89 actor_ref.cast(PreviewInfo::BookRoot(book_root)).await?;
90 let msg = PreviewInfo::OpenPreview {
91 socket_address: Some(socket_address),
92 open_browser_at: open_browser.then_some("".into()),
93 };
94 actor_ref.cast(msg).await?;
95 try_join_actor_handle(handle).await?;
96 Ok(())
97}
98
99pub async fn block_n_yield<F, R>(f: F) -> R
103where
104 F: FnOnce() -> R,
105{
106 let result = block_in_place(f);
107 yield_now().await;
108 result
109}
110
111async fn shut_down_actor_n_log_err<A: Actor>(
112 handle: JoinHandle<ActorRunResult<A>>,
113 actor_ref: ActorRef<A>,
114 err_msg: &'static str,
115) {
116 actor_ref.cancel();
117 if let Err(err) = try_join_actor_handle(handle).await {
118 error!(?err, err_msg);
119 }
120}
121
122async fn try_join_actor_handle<A: Actor>(handle: JoinHandle<ActorRunResult<A>>) -> Result<()> {
123 handle.await?.exit_result
124}
125
126fn open<P: AsRef<OsStr>>(path: P) {
127 match opener::open(path) {
128 Err(err) => {
129 error!(?err, "opening web browser.")
130 }
131 Ok(_) => {
132 info!("Opened web browser.")
133 }
134 }
135}