Skip to content

Commit

Permalink
fix: Rebuilt when config change (#161)
Browse files Browse the repository at this point in the history
* fix: rebuild when config file changes
  • Loading branch information
rochacbruno authored Nov 23, 2024
1 parent 3b5c7d0 commit 51e323d
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 101 deletions.
39 changes: 16 additions & 23 deletions src/content.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Item = (&String, Vec<Content>)> {
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<Content>)> =
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));
}
}
Expand Down Expand Up @@ -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}");
}
}

Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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}");
}
}
}
8 changes: 4 additions & 4 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 {
Expand Down Expand Up @@ -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));
}
}
2 changes: 1 addition & 1 deletion src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ pub fn start(bind_address: &str, output_folder: &Arc<PathBuf>) {
);

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);
Expand Down
129 changes: 56 additions & 73 deletions src/site.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}

Expand All @@ -75,105 +70,93 @@ struct BuildInfo {
}

pub fn generate(
config_path: &std::path::PathBuf,
input_folder: &std::path::Path,
config_path: &Arc<std::path::PathBuf>,
input_folder: &Arc<std::path::PathBuf>,
output_folder: &Arc<std::path::PathBuf>,
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();
}
_ => {}
})
Expand All @@ -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));
Expand Down Expand Up @@ -707,14 +690,14 @@ fn generate_search_index(site_data: &Data, output_folder: &Arc<std::path::PathBu
fn write_build_info(
output_path: &Path,
site_data: &std::sync::MutexGuard<'_, Data>,
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");
Expand Down Expand Up @@ -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 {
Expand All @@ -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,
Expand Down

0 comments on commit 51e323d

Please sign in to comment.