Rust library to generate static websites
5
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor: reduce cloning in assets (#39)

authored by

Erika and committed by
GitHub
8d20f51d 2df56278

+202 -250
+69 -134
crates/maudit/src/assets.rs
··· 2 2 use log::debug; 3 3 use rustc_hash::FxHashSet; 4 4 use std::hash::Hash; 5 + use std::path::Path; 5 6 use std::time::Instant; 6 7 use std::{fs, path::PathBuf}; 7 8 use xxhash_rust::xxh3::xxh3_64; ··· 92 93 return image.clone(); 93 94 } 94 95 95 - let image = Image { 96 - path: image_path.clone(), 97 - assets_dir: self.options.assets_dir.clone(), 98 - output_assets_dir: self.options.output_assets_dir.clone(), 99 - hash: calculate_hash( 100 - &image_path, 101 - Some(&HashConfig { 102 - asset_type: HashAssetType::Image(&options), 103 - hashing_strategy: &self.options.hashing_strategy, 104 - }), 105 - ), 106 - options: if options == ImageOptions::default() { 96 + let image = Image::new( 97 + image_path, 98 + if options == ImageOptions::default() { 107 99 None 108 100 } else { 109 101 Some(options) 110 102 }, 111 - }; 103 + &self.options, 104 + ); 112 105 113 106 self.images.insert(image.clone()); 114 107 ··· 132 125 where 133 126 P: Into<PathBuf>, 134 127 { 135 - let path = script_path.into(); 136 - let script = Script { 137 - path: path.clone(), 138 - assets_dir: self.options.assets_dir.clone(), 139 - output_assets_dir: self.options.output_assets_dir.clone(), 140 - hash: calculate_hash(&path, None), 141 - included: false, 142 - }; 128 + let script = Script::new(script_path.into(), false, &self.options); 143 129 144 130 self.scripts.insert(script.clone()); 145 131 ··· 155 141 where 156 142 P: Into<PathBuf>, 157 143 { 158 - let path = script_path.into(); 159 - let script = Script { 160 - path: path.clone(), 161 - assets_dir: self.options.assets_dir.clone(), 162 - output_assets_dir: self.options.output_assets_dir.clone(), 163 - hash: calculate_hash(&path, None), 164 - included: true, 165 - }; 144 + let script = Script::new(script_path.into(), true, &self.options); 166 145 167 146 self.scripts.insert(script); 168 147 } ··· 189 168 where 190 169 P: Into<PathBuf>, 191 170 { 192 - let path = style_path.into(); 193 - let style = Style { 194 - path: path.clone(), 195 - assets_dir: self.options.assets_dir.clone(), 196 - output_assets_dir: self.options.output_assets_dir.clone(), 197 - hash: calculate_hash( 198 - &path, 199 - Some(&HashConfig { 200 - asset_type: HashAssetType::Style(&options), 201 - hashing_strategy: &self.options.hashing_strategy, 202 - }), 203 - ), 204 - tailwind: options.tailwind, 205 - included: false, 206 - }; 171 + let style = Style::new(style_path.into(), false, &options, &self.options); 207 172 208 173 self.styles.insert(style.clone()); 209 174 ··· 231 196 where 232 197 P: Into<PathBuf>, 233 198 { 234 - let path = style_path.into(); 235 - let hash = calculate_hash( 236 - &path, 237 - Some(&HashConfig { 238 - asset_type: HashAssetType::Style(&options), 239 - hashing_strategy: &self.options.hashing_strategy, 240 - }), 241 - ); 242 - let style = Style { 243 - path: path.clone(), 244 - assets_dir: self.options.assets_dir.clone(), 245 - output_assets_dir: self.options.output_assets_dir.clone(), 246 - hash, 247 - tailwind: options.tailwind, 248 - included: true, 249 - }; 199 + let style = Style::new(style_path.into(), true, &options, &self.options); 250 200 251 201 self.styles.insert(style); 252 202 } 253 203 } 254 204 255 - #[allow(private_bounds)] // Users never interact with the internal trait, so it's fine 256 - pub trait Asset: DynEq + InternalAsset + Sync + Send { 257 - fn build_path(&self) -> PathBuf { 258 - self.output_assets_dir().join(self.final_file_name()) 259 - } 260 - fn url(&self) -> Option<String> { 261 - format!( 262 - "/{}/{}", 263 - self.assets_dir().to_string_lossy(), 264 - self.final_file_name() 265 - ) 266 - .into() 267 - } 268 - 205 + pub trait Asset: DynEq + Sync + Send { 206 + fn build_path(&self) -> &PathBuf; 207 + fn url(&self) -> Option<&String>; 269 208 fn path(&self) -> &PathBuf; 270 - fn hash(&self) -> String; 209 + fn filename(&self) -> &PathBuf; 210 + } 271 211 272 - // TODO: I don't like these next two methods for scripts and styles, we should get this from Rolldown somehow, but I don't know how. 273 - // Our architecture is such that bundling runs after pages, so we can't know the final extension until then. We can't, and I don't want 274 - // to make it so we get assets beforehand because it'd make it less convenient and essentially cause us to act like a bundling framework. 275 - // 276 - // Perhaps it should be done as a post-processing step, like includes, but that'd require moving route finalization to after bundling, 277 - // which I'm not sure I want to do either. Plus, it'd be pretty slow if you have a layout on every page that includes a style/script (a fairly common case). 278 - // 279 - // An additional benefit would with that would also be to be able to avoid generating hashes for these files, but that's a smaller win. 280 - // 281 - // I don't know! - erika, 2025-09-01 212 + macro_rules! implement_asset_trait { 213 + ($type:ty) => { 214 + impl Asset for $type { 215 + fn path(&self) -> &PathBuf { 216 + &self.path 217 + } 282 218 283 - fn final_extension(&self) -> String { 284 - self.path() 285 - .extension() 286 - .map(|ext| ext.to_str().unwrap()) 287 - .unwrap_or_default() 288 - .to_owned() 289 - } 219 + fn filename(&self) -> &PathBuf { 220 + &self.filename 221 + } 290 222 291 - fn final_file_name(&self) -> String { 292 - let file_stem = self.path().file_stem().unwrap().to_str().unwrap(); 293 - let extension = self.final_extension(); 223 + fn build_path(&self) -> &PathBuf { 224 + &self.build_path 225 + } 294 226 295 - if extension.is_empty() { 296 - format!("{}.{}", file_stem, self.hash()) 297 - } else { 298 - format!("{}.{}.{}", file_stem, self.hash(), extension) 227 + fn url(&self) -> Option<&String> { 228 + Some(&self.url) 229 + } 299 230 } 300 - } 231 + }; 301 232 } 233 + 234 + implement_asset_trait!(Image); 235 + implement_asset_trait!(Script); 236 + implement_asset_trait!(Style); 302 237 303 238 struct HashConfig<'a> { 304 239 asset_type: HashAssetType<'a>, ··· 308 243 enum HashAssetType<'a> { 309 244 Image(&'a ImageOptions), 310 245 Style(&'a StyleOptions), 246 + Script, 247 + } 248 + 249 + fn make_filename(path: &Path, hash: &String, extension: Option<&str>) -> PathBuf { 250 + let file_stem = path.file_stem().unwrap(); 251 + 252 + let mut filename = PathBuf::new(); 253 + filename.push(format!("{}.{}", file_stem.to_str().unwrap(), hash)); 254 + 255 + if let Some(extension) = extension { 256 + filename.set_extension(format!("{}.{}", hash, extension)); 257 + } 258 + 259 + filename 260 + } 261 + 262 + fn make_final_url(assets_dir: &Path, file_name: &Path) -> String { 263 + format!("/{}/{}", assets_dir.display(), file_name.display()) 311 264 } 312 265 313 - fn calculate_hash(path: &PathBuf, options: Option<&HashConfig>) -> String { 266 + fn make_final_path(output_assets_dir: &Path, file_name: &Path) -> PathBuf { 267 + output_assets_dir.join(file_name) 268 + } 269 + 270 + fn calculate_hash(path: &Path, options: Option<&HashConfig>) -> String { 314 271 let start_time = Instant::now(); 315 272 let content = if options 316 273 .is_some_and(|cfg| *cfg.hashing_strategy == AssetHashingStrategy::FastImprecise) ··· 356 313 HashAssetType::Style(opts) => { 357 314 buf.push(opts.tailwind as u8); 358 315 } 316 + HashAssetType::Script => { /* No extra options for scripts yet */ } 359 317 } 360 318 } 361 319 ··· 370 328 // TODO: This works, but perhaps we can generate prettier hashes, see https://github.com/rolldown/rolldown/blob/abf62c45d7a69b42dab4bff92095e320b418e9b8/crates/rolldown_utils/src/xxhash.rs 371 329 let hex = format!("{:016x}", hash); 372 330 hex[..5].to_string() 373 - } 374 - 375 - trait InternalAsset { 376 - fn assets_dir(&self) -> &PathBuf; 377 - fn output_assets_dir(&self) -> &PathBuf; 378 331 } 379 332 380 333 impl Hash for dyn Asset { ··· 471 424 let mut page_assets = PageAssets::default(); 472 425 473 426 let image = page_assets.add_image(temp_dir.join("image.png")); 474 - let image_hash = image.hash.clone(); 475 - assert!(image.url().unwrap().contains(&image_hash)); 427 + assert!(image.url().unwrap().contains(&image.hash)); 476 428 477 429 let script = page_assets.add_script(temp_dir.join("script.js")); 478 - let script_hash = script.hash.clone(); 479 - assert!(script.url().unwrap().contains(&script_hash)); 430 + assert!(script.url().unwrap().contains(&script.hash)); 480 431 481 432 let style = page_assets.add_style(temp_dir.join("style.css")); 482 - let style_hash = style.hash.clone(); 483 - assert!(style.url().unwrap().contains(&style_hash)); 433 + assert!(style.url().unwrap().contains(&style.hash)); 484 434 } 485 435 486 436 #[test] ··· 489 439 let mut page_assets = PageAssets::default(); 490 440 491 441 let image = page_assets.add_image(temp_dir.join("image.png")); 492 - let image_hash = image.hash.clone(); 493 - assert!(image.build_path().to_string_lossy().contains(&image_hash)); 442 + assert!(image.build_path().to_string_lossy().contains(&image.hash)); 494 443 495 444 let script = page_assets.add_script(temp_dir.join("script.js")); 496 - let script_hash = script.hash.clone(); 497 - assert!(script.build_path().to_string_lossy().contains(&script_hash)); 445 + assert!(script.build_path().to_string_lossy().contains(&script.hash)); 498 446 499 447 let style = page_assets.add_style(temp_dir.join("style.css")); 500 - let style_hash = style.hash.clone(); 501 - assert!(style.build_path().to_string_lossy().contains(&style_hash)); 448 + assert!(style.build_path().to_string_lossy().contains(&style.hash)); 502 449 } 503 450 504 451 #[test] ··· 658 605 // Write first content and get hash 659 606 std::fs::write(&style_path, "body { background: red; }").unwrap(); 660 607 let style1 = page_assets.add_style(&style_path); 661 - let hash1 = style1.hash.clone(); 608 + let hash1 = style1.hash; 662 609 663 610 // Write different content and get new hash 664 611 std::fs::write(&style_path, "body { background: green; }").unwrap(); 665 - let style2 = Style { 666 - path: style_path.clone(), 667 - assets_dir: assets_options.assets_dir.clone(), 668 - output_assets_dir: assets_options.output_assets_dir.clone(), 669 - hash: calculate_hash( 670 - &style_path, 671 - Some(&HashConfig { 672 - asset_type: HashAssetType::Style(&StyleOptions::default()), 673 - hashing_strategy: &AssetHashingStrategy::Precise, 674 - }), 675 - ), 676 - tailwind: false, 677 - included: false, 678 - }; 612 + let style2 = page_assets.add_style(&style_path); 613 + let hash2 = style2.hash; 679 614 680 615 assert_ne!( 681 - hash1, style2.hash, 616 + hash1, hash2, 682 617 "Different content should produce different hashes" 683 618 ); 684 619 }
+49 -37
crates/maudit/src/assets/image.rs
··· 7 7 use thumbhash::{rgba_to_thumb_hash, thumb_hash_to_average_rgba, thumb_hash_to_rgba}; 8 8 9 9 use super::image_cache::ImageCache; 10 - use crate::assets::{Asset, InternalAsset}; 10 + use crate::assets::{ 11 + HashAssetType, HashConfig, PageAssetsOptions, calculate_hash, make_filename, make_final_path, 12 + make_final_url, 13 + }; 11 14 use crate::is_dev; 12 15 13 16 #[derive(Clone, Debug, PartialEq, Eq, Hash)] ··· 63 66 #[derive(Clone, Debug, Hash, PartialEq, Eq)] 64 67 pub struct Image { 65 68 pub path: PathBuf, 66 - pub(crate) assets_dir: PathBuf, 67 - pub(crate) output_assets_dir: PathBuf, 68 69 pub(crate) hash: String, 69 70 pub(crate) options: Option<ImageOptions>, 71 + 72 + pub(crate) filename: PathBuf, 73 + pub(crate) url: String, 74 + pub(crate) build_path: PathBuf, 70 75 } 71 76 72 77 impl Image { 78 + pub fn new( 79 + path: PathBuf, 80 + image_options: Option<ImageOptions>, 81 + page_assets_options: &PageAssetsOptions, 82 + ) -> Self { 83 + let hash = calculate_hash( 84 + &path, 85 + Some(&HashConfig { 86 + asset_type: HashAssetType::Image( 87 + image_options.as_ref().unwrap_or(&ImageOptions::default()), 88 + ), 89 + hashing_strategy: &page_assets_options.hashing_strategy, 90 + }), 91 + ); 92 + 93 + let filename = make_filename( 94 + &path, 95 + &hash, 96 + image_options 97 + .as_ref() 98 + .and_then(|opts| opts.format.as_ref().map(|f| f.extension().into())) 99 + .or_else(|| { 100 + path.extension() 101 + .and_then(|ext| ext.to_str()) 102 + .map(|s| s.to_lowercase()) 103 + }) 104 + .as_deref(), 105 + ); 106 + let build_path = make_final_path(&page_assets_options.output_assets_dir, &filename); 107 + let url = make_final_url(&page_assets_options.assets_dir, &filename); 108 + 109 + Self { 110 + path, 111 + hash, 112 + options: image_options.clone(), 113 + filename, 114 + url, 115 + build_path, 116 + } 117 + } 118 + 73 119 /// Get a placeholder for the image, which can be used for low-quality image placeholders (LQIP) or similar techniques. 74 120 /// 75 121 /// This uses the [ThumbHash](https://evanw.github.io/thumbhash/) algorithm to generate a very small placeholder image. ··· 381 427 382 428 bytes 383 429 } 384 - 385 - impl InternalAsset for Image { 386 - fn assets_dir(&self) -> &PathBuf { 387 - &self.assets_dir 388 - } 389 - 390 - fn output_assets_dir(&self) -> &PathBuf { 391 - &self.output_assets_dir 392 - } 393 - } 394 - 395 - impl Asset for Image { 396 - fn path(&self) -> &PathBuf { 397 - &self.path 398 - } 399 - 400 - fn hash(&self) -> String { 401 - self.hash.clone() 402 - } 403 - 404 - fn final_extension(&self) -> String { 405 - if let Some(options) = &self.options 406 - && let Some(format) = &options.format 407 - { 408 - format.extension() 409 - } else { 410 - self.path() 411 - .extension() 412 - .and_then(|ext| ext.to_str()) 413 - .unwrap_or_default() 414 - } 415 - .to_string() 416 - } 417 - }
+9 -9
crates/maudit/src/assets/image_cache.rs
··· 28 28 /// Cache for placeholder data (thumbhash, etc.) 29 29 placeholders: FxHashMap<PathBuf, PlaceholderCacheEntry>, 30 30 /// Cache for transformed images (path + options -> cached file path) 31 - transformed: FxHashMap<String, TransformedImageCacheEntry>, 31 + transformed: FxHashMap<PathBuf, TransformedImageCacheEntry>, 32 32 } 33 33 34 34 pub struct ImageCache { ··· 114 114 let entry = TransformedImageCacheEntry { 115 115 cached_path: PathBuf::from(cached_path_str), 116 116 }; 117 - manifest.transformed.insert(cache_key.to_string(), entry); 117 + manifest.transformed.insert(PathBuf::from(cache_key), entry); 118 118 } 119 119 } 120 120 _ => {} ··· 161 161 for (cache_key, entry) in &self.manifest.transformed { 162 162 content.push_str(&format!( 163 163 "{}={}\n", 164 - cache_key, 164 + cache_key.to_string_lossy(), 165 165 entry.cached_path.to_string_lossy() 166 166 )); 167 167 } ··· 195 195 } 196 196 197 197 /// Get cached transformed image path or None if not found 198 - pub fn get_transformed_image(final_filename: &str) -> Option<PathBuf> { 198 + pub fn get_transformed_image(final_filename: &Path) -> Option<PathBuf> { 199 199 let cache = Self::get().lock().ok()?; 200 200 let entry = cache.manifest.transformed.get(final_filename)?; 201 201 ··· 210 210 211 211 debug!( 212 212 "Transformed image cache hit for {} -> {}", 213 - final_filename, 213 + final_filename.display(), 214 214 entry.cached_path.display() 215 215 ); 216 216 Some(entry.cached_path.clone()) 217 217 } 218 218 219 219 /// Cache a transformed image 220 - pub fn cache_transformed_image(final_filename: &str, cached_path: PathBuf) { 220 + pub fn cache_transformed_image(final_filename: &Path, cached_path: PathBuf) { 221 221 if let Ok(mut cache) = Self::get().lock() { 222 222 let entry = TransformedImageCacheEntry { 223 223 cached_path: cached_path.clone(), ··· 226 226 cache 227 227 .manifest 228 228 .transformed 229 - .insert(final_filename.to_string(), entry); 229 + .insert(final_filename.to_path_buf(), entry); 230 230 cache.save_manifest(); 231 231 debug!( 232 232 "Cached transformed image {} -> {}", 233 - final_filename, 233 + final_filename.display(), 234 234 cached_path.display() 235 235 ); 236 236 } 237 237 } 238 238 239 239 /// Generate a cache path for a transformed image 240 - pub fn generate_cache_path(final_filename: &str) -> PathBuf { 240 + pub fn generate_cache_path(final_filename: &Path) -> PathBuf { 241 241 if let Ok(cache) = Self::get().lock() { 242 242 cache.cache_dir.join(final_filename) 243 243 } else {
+26 -30
crates/maudit/src/assets/script.rs
··· 1 1 use std::path::PathBuf; 2 2 3 - use crate::assets::{Asset, InternalAsset}; 3 + use crate::assets::{ 4 + HashAssetType, HashConfig, PageAssetsOptions, calculate_hash, make_filename, make_final_path, 5 + make_final_url, 6 + }; 4 7 5 8 #[derive(Clone, PartialEq, Eq, Hash)] 6 9 #[non_exhaustive] 7 10 pub struct Script { 8 11 pub path: PathBuf, 9 - pub(crate) assets_dir: PathBuf, 10 - pub(crate) output_assets_dir: PathBuf, 11 12 pub(crate) hash: String, 12 13 pub(crate) included: bool, 13 - } 14 14 15 - impl InternalAsset for Script { 16 - fn assets_dir(&self) -> &PathBuf { 17 - &self.assets_dir 18 - } 19 - 20 - fn output_assets_dir(&self) -> &PathBuf { 21 - &self.output_assets_dir 22 - } 15 + pub(crate) filename: PathBuf, 16 + pub(crate) url: String, 17 + pub(crate) build_path: PathBuf, 23 18 } 24 19 25 - impl Asset for Script { 26 - fn path(&self) -> &PathBuf { 27 - &self.path 28 - } 29 - 30 - fn hash(&self) -> String { 31 - self.hash.clone() 32 - } 20 + impl Script { 21 + pub fn new(path: PathBuf, included: bool, page_assets_options: &PageAssetsOptions) -> Self { 22 + let hash = calculate_hash( 23 + &path, 24 + Some(&HashConfig { 25 + asset_type: HashAssetType::Script, 26 + hashing_strategy: &page_assets_options.hashing_strategy, 27 + }), 28 + ); 33 29 34 - fn final_extension(&self) -> String { 35 - let current_extension = self 36 - .path() 37 - .extension() 38 - .and_then(|ext| ext.to_str()) 39 - .unwrap_or_default(); 30 + let filename = make_filename(&path, &hash, Some("js")); 31 + let build_path = make_final_path(&page_assets_options.output_assets_dir, &filename); 32 + let url = make_final_url(&page_assets_options.assets_dir, &filename); 40 33 41 - match current_extension { 42 - "ts" => "js", 43 - ext => ext, 34 + Self { 35 + path, 36 + hash, 37 + included, 38 + filename, 39 + url, 40 + build_path, 44 41 } 45 - .to_string() 46 42 } 47 43 }
+34 -18
crates/maudit/src/assets/style.rs
··· 1 1 use std::path::PathBuf; 2 2 3 - use crate::assets::{Asset, InternalAsset}; 3 + use crate::assets::{ 4 + HashAssetType, HashConfig, PageAssetsOptions, calculate_hash, make_filename, make_final_path, 5 + make_final_url, 6 + }; 4 7 5 8 #[derive(Clone, PartialEq, Eq, Hash, Default)] 6 9 pub struct StyleOptions { ··· 11 14 #[non_exhaustive] 12 15 pub struct Style { 13 16 pub path: PathBuf, 14 - pub(crate) assets_dir: PathBuf, 15 - pub(crate) output_assets_dir: PathBuf, 16 17 pub(crate) hash: String, 17 18 pub(crate) tailwind: bool, 18 19 pub(crate) included: bool, 19 - } 20 - 21 - impl InternalAsset for Style { 22 - fn assets_dir(&self) -> &PathBuf { 23 - &self.assets_dir 24 - } 25 20 26 - fn output_assets_dir(&self) -> &PathBuf { 27 - &self.output_assets_dir 28 - } 21 + pub(crate) filename: PathBuf, 22 + pub(crate) url: String, 23 + pub(crate) build_path: PathBuf, 29 24 } 30 25 31 - impl Asset for Style { 32 - fn path(&self) -> &PathBuf { 33 - &self.path 34 - } 26 + impl Style { 27 + pub fn new( 28 + path: PathBuf, 29 + included: bool, 30 + style_options: &StyleOptions, 31 + page_assets_options: &PageAssetsOptions, 32 + ) -> Self { 33 + let hash = calculate_hash( 34 + &path, 35 + Some(&HashConfig { 36 + asset_type: HashAssetType::Style(style_options), 37 + hashing_strategy: &page_assets_options.hashing_strategy, 38 + }), 39 + ); 35 40 36 - fn hash(&self) -> String { 37 - self.hash.clone() 41 + let filename = make_filename(&path, &hash, Some("css")); 42 + let build_path = make_final_path(&page_assets_options.output_assets_dir, &filename); 43 + let url = make_final_url(&page_assets_options.assets_dir, &filename); 44 + 45 + Self { 46 + path, 47 + tailwind: style_options.tailwind, 48 + hash, 49 + included, 50 + filename, 51 + url, 52 + build_path, 53 + } 38 54 } 39 55 }
+14 -21
crates/maudit/src/build.rs
··· 325 325 .map(|style| InputItem { 326 326 name: Some( 327 327 style 328 - .final_file_name() 329 - .strip_suffix(&format!( 330 - ".{}", 331 - style 332 - .path() 333 - .extension() 334 - .map(|ext| ext.to_str().unwrap()) 335 - .unwrap_or("") 336 - )) 337 - .unwrap_or(&style.final_file_name()) 328 + .filename() 329 + .with_extension("") 330 + .to_string_lossy() 338 331 .to_string(), 339 332 ), 340 333 import: { style.path().to_string_lossy().to_string() }, ··· 347 340 import: script.path().to_string_lossy().to_string(), 348 341 name: Some( 349 342 script 350 - .final_file_name() 351 - .strip_suffix(&format!(".{}", script.final_extension())) 352 - .unwrap_or(&script.final_file_name()) 343 + .filename() 344 + .with_extension("") 345 + .to_string_lossy() 353 346 .to_string(), 354 347 ), 355 348 }) ··· 405 398 let start_time = Instant::now(); 406 399 build_pages_images.par_iter().for_each(|image| { 407 400 let start_process = Instant::now(); 408 - let dest_path: PathBuf = image.build_path(); 401 + let dest_path: &PathBuf = image.build_path(); 409 402 410 403 if let Some(image_options) = &image.options { 411 - let final_filename = image.final_file_name(); 404 + let final_filename = image.filename(); 412 405 413 406 // Check cache for transformed images 414 - if let Some(cached_path) = ImageCache::get_transformed_image(&final_filename) { 407 + if let Some(cached_path) = ImageCache::get_transformed_image(final_filename) { 415 408 // Copy from cache instead of processing 416 - if fs::copy(&cached_path, &dest_path).is_ok() { 409 + if fs::copy(&cached_path, dest_path).is_ok() { 417 410 info!(target: "assets", "{} -> {} (from cache) {}", image.path().to_string_lossy(), dest_path.to_string_lossy().dimmed(), format_elapsed_time(start_process.elapsed(), &route_format_options).dimmed()); 418 411 return; 419 412 } 420 413 } 421 414 422 415 // Generate cache path for transformed image 423 - let cache_path = ImageCache::generate_cache_path(&final_filename); 416 + let cache_path = ImageCache::generate_cache_path(final_filename); 424 417 425 418 // Process image directly to cache 426 419 process_image(image, &cache_path, image_options); 427 420 428 421 // Copy from cache to destination 429 - if fs::copy(&cache_path, &dest_path).is_ok() { 422 + if fs::copy(&cache_path, dest_path).is_ok() { 430 423 // Cache the processed image path 431 - ImageCache::cache_transformed_image(&final_filename, cache_path); 424 + ImageCache::cache_transformed_image(final_filename, cache_path); 432 425 } else { 433 426 debug!("Failed to copy from cache {} to dest {}", cache_path.display(), dest_path.display()); 434 427 } 435 428 } else if !dest_path.exists() { 436 429 // TODO: Check if copying should be done in this parallel iterator, I/O doesn't benefit from parallelism so having those tasks here might just be slowing processing 437 - fs::copy(image.path(), &dest_path).unwrap_or_else(|e| { 430 + fs::copy(image.path(), dest_path).unwrap_or_else(|e| { 438 431 panic!( 439 432 "Failed to copy image from {} to {}: {}", 440 433 image.path().to_string_lossy(),
+1 -1
crates/maudit/src/content/markdown.rs
··· 367 367 let resolved = parent.join(dest_url.to_string()); 368 368 route_ctx 369 369 .as_mut() 370 - .and_then(|ctx| ctx.assets.add_image(resolved).url()) 370 + .and_then(|ctx| ctx.assets.add_image(resolved).url().cloned()) 371 371 }) 372 372 .map(|image_url| { 373 373 Event::Start(Tag::Image {