From 51e323d01af499d0f502d6b615dc2b64044f9e8d Mon Sep 17 00:00:00 2001 From: Bruno Rocha Date: Sat, 23 Nov 2024 15:20:25 +0000 Subject: [PATCH] fix: Rebuilt when config change (#161) * fix: rebuild when config file changes --- src/content.rs | 39 ++++++--------- src/main.rs | 8 +-- src/server.rs | 2 +- src/site.rs | 129 +++++++++++++++++++++---------------------------- 4 files changed, 77 insertions(+), 101 deletions(-) diff --git a/src/content.rs b/src/content.rs index 675eb87..6c216f1 100644 --- a/src/content.rs +++ b/src/content.rs @@ -36,35 +36,28 @@ impl GroupedContent { self.map.entry(key) } - /// Sort tag map by number of contents - /// Sort archive map by date - /// Sort author map by author name - /// Sort stream map by stream name + pub fn sort_all(&mut self) { + for contents in self.map.values_mut() { + contents.sort_by(|a, b| b.date.cmp(&a.date)); + } + } + pub fn iter(&self) -> impl Iterator)> { - let mut vec = Vec::new(); + // Assuming the content is already sorted by date on a previous call of .sort_all + let mut vec: Vec<(&String, Vec)> = + self.map.iter().map(|(k, v)| (k, v.clone())).collect(); + match self.kind { Kind::Tag => { - for (tag, contents) in &self.map { - let mut contents = contents.clone(); - contents.sort_by(|a, b| b.date.cmp(&a.date)); - vec.push((tag, contents)); - } + // sort by number of contents vec.sort_by(|a, b| b.1.len().cmp(&a.1.len())); } Kind::Archive => { - for (text, contents) in &self.map { - let mut contents = contents.clone(); - contents.sort_by(|a, b| b.date.cmp(&a.date)); - vec.push((text, contents)); - } + // sort by year, newest first vec.sort_by(|a, b| b.0.cmp(a.0)); } Kind::Author | Kind::Stream => { - for (text, contents) in &self.map { - let mut contents = contents.clone(); - contents.sort_by(|a, b| b.date.cmp(&a.date)); - vec.push((text, contents)); - } + // sort alphabetically vec.sort_by(|a, b| a.0.cmp(b.0)); } } @@ -532,7 +525,7 @@ Second Title for filename in filenames { let path = Path::new(filename); let slug = get_slug(&frontmatter, path); - assert_eq!(slug, "my-file", "Failed for filename: {}", filename); + assert_eq!(slug, "my-file", "Failed for filename: {filename}"); } } @@ -584,7 +577,7 @@ Second Title #[test] fn test_get_tags_with_empty_str() { let mut frontmatter = Frontmatter::new(); - frontmatter.insert("tags".to_string(), Value::String("".to_string())); + frontmatter.insert("tags".to_string(), Value::String(String::new())); let tags = get_tags(&frontmatter); assert!(tags.is_empty()); @@ -820,7 +813,7 @@ Second Title for input in inputs { let date = try_to_parse_date(input); - assert!(date.is_ok(), "Failed for input: {}", input); + assert!(date.is_ok(), "Failed for input: {input}"); } } } diff --git a/src/main.rs b/src/main.rs index 5461736..fec8081 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,7 +17,7 @@ mod tera_functions; fn main() { let args = cli::Cli::parse(); - let input_folder = args.input_folder; + let input_folder = Arc::new(args.input_folder); let output_folder = Arc::new(args.output_folder); let serve = args.serve; let watch = args.watch; @@ -28,9 +28,9 @@ fn main() { } let config_path = if args.config.starts_with('.') || args.config.starts_with('/') { - PathBuf::new().join(args.config) + Arc::new(PathBuf::new().join(args.config)) } else { - input_folder.join(args.config) + Arc::new(input_folder.join(args.config)) }; let env = Env::default().default_filter_or(match verbose { @@ -75,6 +75,6 @@ fn main() { // Serve the site if the flag was provided if serve && !watch { info!("Starting built-in HTTP server..."); - server::start(bind_address, &output_folder); + server::start(bind_address, &Arc::clone(&output_folder)); } } diff --git a/src/server.rs b/src/server.rs index a8ea26b..5958a19 100644 --- a/src/server.rs +++ b/src/server.rs @@ -14,7 +14,7 @@ pub fn start(bind_address: &str, output_folder: &Arc) { ); for request in server.incoming_requests() { - let response = match handle_request(&request, output_folder) { + let response = match handle_request(&request, output_folder.as_path()) { Ok(response) => response, Err(err) => { error!("Error handling request: {}", err); diff --git a/src/site.rs b/src/site.rs index 433e9e8..2e996fc 100644 --- a/src/site.rs +++ b/src/site.rs @@ -53,15 +53,10 @@ impl Data { pub fn sort_all(&mut self) { self.posts.sort_by(|a, b| b.date.cmp(&a.date)); self.pages.sort_by(|a, b| b.title.cmp(&a.title)); - } - - pub fn clear_all(&mut self) { - self.posts.clear(); - self.pages.clear(); - self.tag.map.clear(); - self.archive.map.clear(); - self.author.map.clear(); - self.stream.map.clear(); + self.tag.sort_all(); + self.archive.sort_all(); + self.author.sort_all(); + self.stream.sort_all(); } } @@ -75,105 +70,93 @@ struct BuildInfo { } pub fn generate( - config_path: &std::path::PathBuf, - input_folder: &std::path::Path, + config_path: &Arc, + input_folder: &Arc, output_folder: &Arc, - watch: bool, // New parameter for watching, - serve: bool, // Is running on server mode + watch: bool, + serve: bool, bind_address: &str, ) { - let config_str = fs::read_to_string(config_path).unwrap_or_else(|e| { - info!( - "Unable to read '{}', assuming defaults.: {}", - &config_path.display(), - e - ); - String::new() - }); - if config_str.is_empty() { - info!("Config loaded from: defaults"); - } else { - info!("Config loaded from: {}", config_path.display()); - } - let site_data = Arc::new(Mutex::new(Data::new(&config_str))); + let moved_input_folder = Arc::clone(input_folder); + let moved_output_folder = Arc::clone(output_folder); + let moved_config_path = Arc::clone(config_path); - // Define the content directory - let content_dir = { - let site_data = site_data.lock().unwrap(); - Some(input_folder.join(site_data.site.content_path.clone())) - } - .filter(|path| path.is_dir()) // Take if exists - .unwrap_or_else(|| input_folder.to_path_buf()); - // Fallback to input_folder if not + let rebuild = { + move || { + let start_time = std::time::Instant::now(); - // Function to trigger site regeneration - let rebuild_site = { - let start_time = std::time::Instant::now(); + let config_str = fs::read_to_string(moved_config_path.as_path()).unwrap_or_else(|e| { + info!( + "Unable to read '{}', assuming defaults.: {}", + &moved_config_path.display(), + e + ); + String::new() + }); + if config_str.is_empty() { + info!("Config loaded from: defaults"); + } else { + info!("Config loaded from: {}", moved_config_path.display()); + } - let content_dir = content_dir.clone(); - let output_folder = Arc::clone(output_folder); - let input_folder = input_folder.to_path_buf(); - let site_data = site_data.clone(); + let site_data = Arc::new(Mutex::new(Data::new(&config_str))); + let content_dir = { + let site_data = site_data.lock().unwrap(); + Some(moved_input_folder.join(site_data.site.content_path.clone())) + } + .filter(|path| path.is_dir()) // Take if exists + .unwrap_or_else(|| moved_input_folder.to_path_buf()); // Fallback to input_folder if not - move || { let mut site_data = site_data.lock().unwrap(); - // cleanup before rebuilding, otherwise we get duplicated content - site_data.clear_all(); let fragments = collect_fragments(&content_dir); collect_content(&content_dir, &mut site_data, &fragments); - - // Detect slug collision - detect_slug_collision(&site_data); - - // Feed back_links - collect_back_links(&mut site_data); - site_data.sort_all(); + detect_slug_collision(&site_data); // Detect slug collision and warn user + collect_back_links(&mut site_data); - // Create the output directory let site_path = site_data.site.site_path.clone(); - let output_path = output_folder.join(site_path); + let output_path = moved_output_folder.join(site_path); if let Err(e) = fs::create_dir_all(&output_path) { error!("Unable to create output directory: {}", e); process::exit(1); } - // Initialize Tera templates - let tera = initialize_tera(&input_folder, &site_data); - - // Render templates + let tera = initialize_tera(&moved_input_folder, &site_data); if let Err(e) = render_templates(&content_dir, &site_data, &tera, &output_path) { error!("Failed to render templates: {}", e); process::exit(1); } - // Copy static folder if present - handle_static_artifacts(&input_folder, &site_data, &output_folder, &content_dir); + handle_static_artifacts( + &moved_input_folder, + &site_data, + &moved_output_folder, + &content_dir, + ); if site_data.site.enable_search { - generate_search_index(&site_data, &output_folder); + generate_search_index(&site_data, &moved_output_folder); } let end_time = start_time.elapsed().as_secs_f64(); - write_build_info(&output_path, &site_data, &end_time); + write_build_info(&output_path, &site_data, end_time); debug!("Site generated in {:.2}s", end_time); - info!("Site generated at: {}/", output_folder.display()); + info!("Site generated at: {}/", moved_output_folder.display()); } }; // Initial site generation - rebuild_site(); + rebuild(); - // If watch flag is enabled, start hotwatch if watch { let mut hotwatch = Hotwatch::new().expect("Failed to initialize hotwatch!"); - + let watch_folder = Arc::clone(input_folder).as_path().to_path_buf(); // Watch the input folder for changes hotwatch - .watch(input_folder, move |event: Event| match event.kind { + .watch(watch_folder, move |event: Event| match event.kind { EventKind::Create(_) | EventKind::Modify(_) | EventKind::Remove(_) => { info!("Change detected. Rebuilding site..."); - rebuild_site(); + rebuild(); } _ => {} }) @@ -184,7 +167,7 @@ pub fn generate( // Keep the thread alive for watching if serve { info!("Starting built-in HTTP server..."); - server::start(bind_address, output_folder); + server::start(bind_address, &Arc::clone(output_folder)); } else { loop { std::thread::sleep(std::time::Duration::from_secs(1)); @@ -707,14 +690,14 @@ fn generate_search_index(site_data: &Data, output_folder: &Arc, - end_time: &f64, + end_time: f64, ) { let build_info = BuildInfo { marmite_version: env!("CARGO_PKG_VERSION").to_string(), posts: site_data.posts.len(), pages: site_data.pages.len(), generated_at: chrono::Local::now().to_string(), - elapsed_time: *end_time, + elapsed_time: end_time, }; let build_info_path = output_path.join("marmite.json"); @@ -742,7 +725,7 @@ fn handle_list_page( let mut context = global_context.clone(); context.insert("title", title); context.insert("per_page", &per_page); - context.insert("current_page", &format!("{}.html", output_filename)); + context.insert("current_page", &format!("{output_filename}.html")); // If all_content is empty, ensure we still generate an empty page if total_content == 0 { @@ -753,7 +736,7 @@ fn handle_list_page( context.insert("current_page_number", &1); render_html( "custom_list.html,list.html", - &format!("{}.html", output_filename), + &format!("{output_filename}.html"), tera, &context, output_dir,