add option to specify a callback to make using hot-reloading in a CLI easier

This commit is contained in:
Evan Almloff committed 2023-01-15 11:29:51 -06:00
1 parent 63840bcca4
commit 893d0be1a6
1 file changed
+53 -28
+53 -28
View File
@@ -27,13 +27,12 @@ pub enum HotReloadMsg {
Shutdown, Shutdown,
} }
#[derive(Debug, Clone, Copy)]
pub struct Config<Ctx: HotReloadingContext = HtmlCtx> { pub struct Config<Ctx: HotReloadingContext = HtmlCtx> {
root_path: &'static str, root_path: &'static str,
listening_paths: &'static [&'static str], listening_paths: &'static [&'static str],
excluded_paths: &'static [&'static str], excluded_paths: &'static [&'static str],
log: bool, log: bool,
rebuild_with: Option<&'static str>, rebuild_with: Option<Box<dyn FnMut() -> bool + Send + 'static>>,
phantom: std::marker::PhantomData<Ctx>, phantom: std::marker::PhantomData<Ctx>,
} }
@@ -65,7 +64,7 @@ impl Config<HtmlCtx> {
impl<Ctx: HotReloadingContext> Config<Ctx> { impl<Ctx: HotReloadingContext> Config<Ctx> {
/// Set the root path of the project (where the Cargo.toml file is). This is automatically set by the [`hot_reload_init`] macro. /// Set the root path of the project (where the Cargo.toml file is). This is automatically set by the [`hot_reload_init`] macro.
pub const fn root(self, path: &'static str) -> Self { pub fn root(self, path: &'static str) -> Self {
Self { Self {
root_path: path, root_path: path,
..self ..self
@@ -73,22 +72,37 @@ impl<Ctx: HotReloadingContext> Config<Ctx> {
} }
/// Set whether to enable logs /// Set whether to enable logs
pub const fn with_logging(self, log: bool) -> Self { pub fn with_logging(self, log: bool) -> Self {
Self { log, ..self } Self { log, ..self }
} }
/// Set the command to run to rebuild the project /// Set the command to run to rebuild the project
/// ///
/// For example to restart the application after a change is made, you could use `cargo run` /// For example to restart the application after a change is made, you could use `cargo run`
pub const fn with_rebuild_command(self, rebuild_with: &'static str) -> Self { pub fn with_rebuild_command(self, rebuild_command: &'static str) -> Self {
self.with_rebuild_callback(move || {
execute::shell(rebuild_command)
.spawn()
.expect("Failed to spawn the rebuild command");
true
})
}
/// Set a callback to run to when the project needs to be rebuilt and returns if the server should shut down
///
/// For example a CLI application could rebuild the application when a change is made
pub fn with_rebuild_callback(
self,
rebuild_callback: impl FnMut() -> bool + Send + 'static,
) -> Self {
Self { Self {
rebuild_with: Some(rebuild_with), rebuild_with: Some(Box::new(rebuild_callback)),
..self ..self
} }
} }
/// Set the paths to listen for changes in to trigger hot reloading. If this is a directory it will listen for changes in all files in that directory recursively. /// Set the paths to listen for changes in to trigger hot reloading. If this is a directory it will listen for changes in all files in that directory recursively.
pub const fn with_paths(self, paths: &'static [&'static str]) -> Self { pub fn with_paths(self, paths: &'static [&'static str]) -> Self {
Self { Self {
listening_paths: paths, listening_paths: paths,
..self ..self
@@ -96,7 +110,7 @@ impl<Ctx: HotReloadingContext> Config<Ctx> {
} }
/// Sets paths to ignore changes on. This will override any paths set in the [`Config::with_paths`] method in the case of conflicts. /// Sets paths to ignore changes on. This will override any paths set in the [`Config::with_paths`] method in the case of conflicts.
pub const fn excluded_paths(self, paths: &'static [&'static str]) -> Self { pub fn excluded_paths(self, paths: &'static [&'static str]) -> Self {
Self { Self {
excluded_paths: paths, excluded_paths: paths,
..self ..self
@@ -110,7 +124,7 @@ pub fn init<Ctx: HotReloadingContext + Send + 'static>(cfg: Config<Ctx>) {
root_path, root_path,
listening_paths, listening_paths,
log, log,
rebuild_with, mut rebuild_with,
excluded_paths, excluded_paths,
phantom: _, phantom: _,
} = cfg; } = cfg;
@@ -190,23 +204,31 @@ pub fn init<Ctx: HotReloadingContext + Send + 'static>(cfg: Config<Ctx>) {
.map(|path| crate_dir.join(PathBuf::from(path))) .map(|path| crate_dir.join(PathBuf::from(path)))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let rebuild = || { let mut rebuild = {
if let Some(rebuild_command) = rebuild_with { let aborted = aborted.clone();
*aborted.lock().unwrap() = true; let channels = channels.clone();
if log { move || {
println!("Rebuilding the application..."); if let Some(rebuild_callback) = &mut rebuild_with {
} if log {
execute::shell(rebuild_command) println!("Rebuilding the application...");
.spawn() }
.expect("Failed to spawn the rebuild command"); let shutdown = rebuild_callback();
for channel in &mut *channels.lock().unwrap() { if shutdown {
send_msg(HotReloadMsg::Shutdown, channel); *aborted.lock().unwrap() = true;
}
for channel in &mut *channels.lock().unwrap() {
send_msg(HotReloadMsg::Shutdown, channel);
}
return shutdown;
} else if log {
println!(
"Rebuild needed... shutting down hot reloading.\nManually rebuild the application to view futher changes."
);
} }
} else if log { true
println!(
"Rebuild needed... shutting down hot reloading.\nManually rebuild the application to view futher changes."
);
} }
}; };
@@ -240,7 +262,9 @@ pub fn init<Ctx: HotReloadingContext + Send + 'static>(cfg: Config<Ctx>) {
for path in real_paths { for path in real_paths {
// if this file type cannot be hot reloaded, rebuild the application // if this file type cannot be hot reloaded, rebuild the application
if path.extension().and_then(|p| p.to_str()) != Some("rs") { if path.extension().and_then(|p| p.to_str()) != Some("rs") {
rebuild(); if rebuild() {
return;
}
} }
// find changes to the rsx in the file // find changes to the rsx in the file
match file_map match file_map
@@ -266,9 +290,10 @@ pub fn init<Ctx: HotReloadingContext + Send + 'static>(cfg: Config<Ctx>) {
} }
UpdateResult::NeedsRebuild => { UpdateResult::NeedsRebuild => {
drop(channels); drop(channels);
rebuild(); if rebuild() {
return;
return; }
break;
} }
} }
} }