Rust library to generate static websites

fix: just some clean up

+94 -38
+4 -7
crates/maudit/src/assets/image_cache.rs
··· 338 338 339 339 #[test] 340 340 fn test_build_options_integration() { 341 - use crate::build::options::{AssetsOptions, BuildOptions}; 341 + use crate::build::options::BuildOptions; 342 342 343 343 // Test that BuildOptions can configure the cache directory 344 344 let custom_cache = PathBuf::from("/tmp/custom_maudit_cache"); 345 345 let build_options = BuildOptions { 346 - assets: AssetsOptions { 347 - image_cache_dir: custom_cache.clone(), 348 - ..Default::default() 349 - }, 346 + cache_dir: custom_cache.clone(), 350 347 ..Default::default() 351 348 }; 352 349 353 350 // Create cache with build options 354 - let cache = ImageCache::with_cache_dir(&build_options.assets.image_cache_dir); 351 + let cache = ImageCache::with_cache_dir(&build_options.assets_cache_dir()); 355 352 356 353 // Verify it uses the configured directory 357 - assert_eq!(cache.get_cache_dir(), custom_cache); 354 + assert_eq!(cache.get_cache_dir(), custom_cache.join("assets")); 358 355 } 359 356 360 357 #[test]
+11 -13
crates/maudit/src/build.rs
··· 105 105 // Create a directory for the output 106 106 trace!(target: "build", "Setting up required directories..."); 107 107 108 - // Determine build cache directory 109 - let build_cache_dir = options.assets.image_cache_dir.parent() 110 - .unwrap_or(Path::new("target/maudit_cache")) 111 - .to_path_buf(); 108 + // Use cache directory from options 109 + let build_cache_dir = &options.cache_dir; 112 110 113 - // Load build state for incremental builds 114 - let mut build_state = if is_dev() { 115 - BuildState::load(&build_cache_dir).unwrap_or_else(|e| { 111 + // Load build state for incremental builds (only if incremental is enabled) 112 + let mut build_state = if options.incremental { 113 + BuildState::load(build_cache_dir).unwrap_or_else(|e| { 116 114 debug!(target: "build", "Failed to load build state: {}", e); 117 115 BuildState::new() 118 116 }) ··· 121 119 }; 122 120 123 121 // Determine if this is an incremental build 124 - let is_incremental = is_dev() && changed_files.is_some() && !build_state.asset_to_routes.is_empty(); 122 + let is_incremental = options.incremental && changed_files.is_some() && !build_state.asset_to_routes.is_empty(); 125 123 126 124 let routes_to_rebuild = if is_incremental { 127 125 let changed = changed_files.unwrap(); ··· 192 190 }; 193 191 194 192 // Create the image cache early so it can be shared across routes 195 - let image_cache = ImageCache::with_cache_dir(&options.assets.image_cache_dir); 193 + let image_cache = ImageCache::with_cache_dir(&options.assets_cache_dir()); 196 194 let _ = fs::create_dir_all(image_cache.get_cache_dir()); 197 195 198 196 // Create route_assets_options with the image cache ··· 660 658 ); 661 659 662 660 // Store bundler inputs in build state for next incremental build 663 - if is_dev() { 661 + if options.incremental { 664 662 build_state.bundler_inputs = bundler_inputs 665 663 .iter() 666 664 .map(|input| input.import.clone()) ··· 802 800 info!(target: "SKIP_FORMAT", "{}", ""); 803 801 info!(target: "build", "{}", format!("Build completed in {}", format_elapsed_time(build_start.elapsed(), &section_format_options)).bold()); 804 802 805 - // Save build state for next incremental build 806 - if is_dev() { 807 - if let Err(e) = build_state.save(&build_cache_dir) { 803 + // Save build state for next incremental build (only if incremental is enabled) 804 + if options.incremental { 805 + if let Err(e) = build_state.save(build_cache_dir) { 808 806 warn!(target: "build", "Failed to save build state: {}", e); 809 807 } else { 810 808 debug!(target: "build", "Build state saved to {}", build_cache_dir.join("build_state.json").display());
+79 -18
crates/maudit/src/build/options.rs
··· 36 36 /// assets: AssetsOptions { 37 37 /// assets_dir: "_assets".into(), 38 38 /// tailwind_binary_path: "./node_modules/.bin/tailwindcss".into(), 39 - /// image_cache_dir: ".cache/maudit/images".into(), 40 39 /// ..Default::default() 41 40 /// }, 42 41 /// prefetch: PrefetchOptions { ··· 60 59 /// 61 60 /// At the speed Maudit operates at, not cleaning the output directory may offer a significant performance improvement at the cost of potentially serving stale content. 62 61 pub clean_output_dir: bool, 62 + 63 + /// Whether to enable incremental builds. 64 + /// 65 + /// When enabled, Maudit tracks which assets are used by which routes and only rebuilds 66 + /// routes affected by changed files. This can significantly speed up rebuilds when only 67 + /// a few files have changed. 68 + /// 69 + /// Defaults to `true` in dev mode (`maudit dev`) and `false` in production builds. 70 + pub incremental: bool, 71 + 72 + /// Directory for build cache storage (incremental build state, etc.). 73 + /// 74 + /// Defaults to `target/maudit_cache/{package_name}` where `{package_name}` is derived 75 + /// from the current directory name. 76 + pub cache_dir: PathBuf, 77 + 78 + /// Directory for caching processed assets (images, etc.). 79 + /// 80 + /// If `None`, defaults to `{cache_dir}/assets`. 81 + pub assets_cache_dir: Option<PathBuf>, 63 82 64 83 pub assets: AssetsOptions, 65 84 ··· 124 143 hashing_strategy: self.assets.hashing_strategy, 125 144 } 126 145 } 146 + 147 + /// Returns the directory for caching processed assets (images, etc.). 148 + /// Uses `assets_cache_dir` if set, otherwise defaults to `{cache_dir}/assets`. 149 + pub fn assets_cache_dir(&self) -> PathBuf { 150 + self.assets_cache_dir 151 + .clone() 152 + .unwrap_or_else(|| self.cache_dir.join("assets")) 153 + } 127 154 } 128 155 129 156 #[derive(Clone)] ··· 139 166 /// Note that this value is not automatically joined with the `output_dir` in `BuildOptions`. Use [`BuildOptions::route_assets_options()`] to get a `RouteAssetsOptions` with the correct final path. 140 167 pub assets_dir: PathBuf, 141 168 142 - /// Directory to use for image cache storage. 143 - /// Defaults to `target/maudit_cache/images`. 144 - /// 145 - /// This cache is used to store processed images and their placeholders to speed up subsequent builds. 146 - pub image_cache_dir: PathBuf, 147 - 148 169 /// Strategy to use when hashing assets for fingerprinting. 149 170 /// 150 171 /// Defaults to [`AssetHashingStrategy::Precise`] in production builds, and [`AssetHashingStrategy::FastImprecise`] in development builds. Note that this means that the cache isn't shared between dev and prod builds by default, if you have a lot of assets you may want to set this to the same value in both environments. ··· 164 185 Self { 165 186 tailwind_binary_path: "tailwindcss".into(), 166 187 assets_dir: "_maudit".into(), 167 - image_cache_dir: { 168 - find_target_dir() 169 - .unwrap_or_else(|_| PathBuf::from("target")) 170 - .join("maudit_cache/images") 171 - }, 172 188 hashing_strategy: if is_dev() { 173 189 AssetHashingStrategy::FastImprecise 174 190 } else { ··· 196 212 /// ``` 197 213 impl Default for BuildOptions { 198 214 fn default() -> Self { 215 + let site_name = get_site_name(); 216 + let cache_dir = find_target_dir() 217 + .unwrap_or_else(|_| PathBuf::from("target")) 218 + .join("maudit_cache") 219 + .join(&site_name); 220 + 199 221 Self { 200 222 base_url: None, 201 223 output_dir: "dist".into(), 202 224 static_dir: "static".into(), 203 225 clean_output_dir: true, 226 + incremental: is_dev(), 227 + cache_dir, 228 + assets_cache_dir: None, 204 229 prefetch: PrefetchOptions::default(), 205 230 assets: AssetsOptions::default(), 206 231 sitemap: SitemapOptions::default(), ··· 208 233 } 209 234 } 210 235 236 + /// Get the site name for cache directory purposes. 237 + /// 238 + /// Tries to read the package name from Cargo.toml in the current directory, 239 + /// falling back to the current directory name. 240 + fn get_site_name() -> String { 241 + // Try to read package name from Cargo.toml 242 + if let Ok(cargo_toml) = fs::read_to_string("Cargo.toml") { 243 + // Simple parsing - look for name = "..." in [package] section 244 + let mut in_package = false; 245 + for line in cargo_toml.lines() { 246 + let trimmed = line.trim(); 247 + if trimmed == "[package]" { 248 + in_package = true; 249 + } else if trimmed.starts_with('[') { 250 + in_package = false; 251 + } else if in_package && trimmed.starts_with("name") { 252 + // Parse: name = "package-name" or name = 'package-name' 253 + if let Some(eq_pos) = trimmed.find('=') { 254 + let value = trimmed[eq_pos + 1..].trim(); 255 + let value = value.trim_matches('"').trim_matches('\''); 256 + if !value.is_empty() { 257 + return value.to_string(); 258 + } 259 + } 260 + } 261 + } 262 + } 263 + 264 + // Fallback to current directory name 265 + std::env::current_dir() 266 + .ok() 267 + .and_then(|p| p.file_name().map(|s| s.to_string_lossy().to_string())) 268 + .unwrap_or_else(|| "default".to_string()) 269 + } 270 + 211 271 /// Find the target directory using multiple strategies 212 272 /// 213 273 /// This function tries multiple approaches to locate the target directory: ··· 238 298 let cargo_toml = current.join("Cargo.toml"); 239 299 if cargo_toml.exists() 240 300 && let Ok(content) = fs::read_to_string(&cargo_toml) 241 - && content.contains("[workspace]") { 242 - let workspace_target = current.join("target"); 243 - if workspace_target.exists() { 244 - return Ok(workspace_target); 245 - } 246 - } 301 + && content.contains("[workspace]") 302 + { 303 + let workspace_target = current.join("target"); 304 + if workspace_target.exists() { 305 + return Ok(workspace_target); 306 + } 307 + } 247 308 248 309 // Move up to parent directory 249 310 if !current.pop() {