@@ -12,15 +12,18 @@ use crate::cache::{compute_input_hash, compute_output_hash, load_cache, load_out
1212use crate :: config:: { load_config, OutputType } ;
1313use crate :: deps:: validate_dependencies;
1414use crate :: files:: { ensure_output_dir, validate_input} ;
15+ use crate :: optimization:: OptimizationMode ;
1516use crate :: pipeline:: { Pipeline , StrategyStep } ;
1617use crate :: strategies:: select_strategy;
1718use crate :: template:: { init_tera, validate_templates} ;
1819
1920/// Run the full build pipeline.
2021///
2122/// Transforms fail fast: the first transform error aborts the build and returns an error.
22- pub fn run ( config_path : & str , dry_run : bool ) -> Result < ( ) > {
23- run_impl ( config_path, dry_run, false )
23+ ///
24+ /// `optimization` overrides the mode from the config file when `Some`.
25+ pub fn run ( config_path : & str , dry_run : bool , optimization : Option < OptimizationMode > ) -> Result < ( ) > {
26+ run_impl ( config_path, dry_run, false , optimization)
2427}
2528
2629/// Run the build pipeline in resilient mode.
@@ -29,10 +32,10 @@ pub fn run(config_path: &str, dry_run: bool) -> Result<()> {
2932/// aborting the build. Suitable for watch-mode rebuilds where a transient
3033/// transform error should not stop the file watcher.
3134pub fn run_resilient ( config_path : & str ) -> Result < ( ) > {
32- run_impl ( config_path, false , true )
35+ run_impl ( config_path, false , true , None )
3336}
3437
35- fn run_impl ( config_path : & str , dry_run : bool , resilient : bool ) -> Result < ( ) > {
38+ fn run_impl ( config_path : & str , dry_run : bool , resilient : bool , optimization : Option < OptimizationMode > ) -> Result < ( ) > {
3639 if dry_run {
3740 info ! ( "Dry-run mode enabled — no files will be created and no commands will be executed" ) ;
3841 }
@@ -41,6 +44,10 @@ fn run_impl(config_path: &str, dry_run: bool, resilient: bool) -> Result<()> {
4144 let config = load_config ( config_path) ?;
4245 info ! ( "Loaded config successfully" ) ;
4346
47+ // CLI flag takes precedence over config file; fall back to config value.
48+ let opt_mode = optimization. unwrap_or ( config. optimization ) ;
49+ info ! ( optimization = %opt_mode, "Using optimization mode" ) ;
50+
4451 let canonical_input = validate_input ( & config. input ) ?;
4552
4653 // Validate required system dependencies after confirming the config and input
@@ -294,12 +301,12 @@ mod tests {
294301 #[ ignore = "requires pandoc to be installed" ]
295302 fn test_build_run_succeeds ( ) {
296303 let ( f, _dir) = valid_config_file ( ) ;
297- assert ! ( run( f. path( ) . to_str( ) . unwrap( ) , false ) . is_ok( ) ) ;
304+ assert ! ( run( f. path( ) . to_str( ) . unwrap( ) , false , None ) . is_ok( ) ) ;
298305 }
299306
300307 #[ test]
301308 fn test_build_run_missing_config ( ) {
302- let result = run ( "/nonexistent/renderflow.yaml" , false ) ;
309+ let result = run ( "/nonexistent/renderflow.yaml" , false , None ) ;
303310 assert ! ( result. is_err( ) ) ;
304311 }
305312
@@ -314,7 +321,7 @@ mod tests {
314321 let mut f = NamedTempFile :: new ( ) . expect ( "failed to create temp file" ) ;
315322 f. write_all ( config_content. as_bytes ( ) )
316323 . expect ( "failed to write config" ) ;
317- let result = run ( f. path ( ) . to_str ( ) . unwrap ( ) , false ) ;
324+ let result = run ( f. path ( ) . to_str ( ) . unwrap ( ) , false , None ) ;
318325 assert ! ( result. is_err( ) , "expected error when input file is missing" ) ;
319326 let msg = format ! ( "{}" , result. unwrap_err( ) ) ;
320327 assert ! ( msg. contains( "Input file not found" ) , "unexpected error: {}" , msg) ;
@@ -334,7 +341,7 @@ mod tests {
334341 let mut f = NamedTempFile :: new ( ) . expect ( "failed to create temp file" ) ;
335342 f. write_all ( config_content. as_bytes ( ) )
336343 . expect ( "failed to write config" ) ;
337- let result = run ( f. path ( ) . to_str ( ) . unwrap ( ) , false ) ;
344+ let result = run ( f. path ( ) . to_str ( ) . unwrap ( ) , false , None ) ;
338345 assert ! ( result. is_err( ) , "expected error for unsupported format" ) ;
339346 let msg = format ! ( "{}" , result. unwrap_err( ) ) ;
340347 assert ! (
@@ -363,15 +370,15 @@ mod tests {
363370 . write_all ( config_content. as_bytes ( ) )
364371 . unwrap ( ) ;
365372
366- assert ! ( run( config_file. path( ) . to_str( ) . unwrap( ) , false ) . is_ok( ) ) ;
373+ assert ! ( run( config_file. path( ) . to_str( ) . unwrap( ) , false , None ) . is_ok( ) ) ;
367374 assert ! ( output_dir. join( "input.html" ) . exists( ) ) ;
368375 }
369376
370377 #[ test]
371378 fn test_dry_run_succeeds_without_pandoc ( ) {
372379 let ( f, dir) = valid_config_file ( ) ;
373380 let output_dir = dir. path ( ) . join ( "dist" ) ;
374- let result = run ( f. path ( ) . to_str ( ) . unwrap ( ) , true ) ;
381+ let result = run ( f. path ( ) . to_str ( ) . unwrap ( ) , true , None ) ;
375382 assert ! ( result. is_ok( ) , "dry-run should succeed: {:?}" , result) ;
376383 // No output directory should have been created in dry-run mode
377384 assert ! ( !output_dir. exists( ) , "output directory must not be created in dry-run mode" ) ;
@@ -381,14 +388,14 @@ mod tests {
381388 fn test_dry_run_does_not_create_output_files ( ) {
382389 let ( f, dir) = valid_config_file ( ) ;
383390 let output_dir = dir. path ( ) . join ( "dist" ) ;
384- run ( f. path ( ) . to_str ( ) . unwrap ( ) , true ) . expect ( "dry-run should not fail" ) ;
391+ run ( f. path ( ) . to_str ( ) . unwrap ( ) , true , None ) . expect ( "dry-run should not fail" ) ;
385392 // The dist directory and any rendered files must not exist
386393 assert ! ( !output_dir. exists( ) , "output directory must not be created in dry-run mode" ) ;
387394 }
388395
389396 #[ test]
390397 fn test_dry_run_missing_config_still_errors ( ) {
391- let result = run ( "/nonexistent/renderflow.yaml" , true ) ;
398+ let result = run ( "/nonexistent/renderflow.yaml" , true , None ) ;
392399 assert ! ( result. is_err( ) , "dry-run with missing config should still error" ) ;
393400 }
394401
@@ -419,7 +426,7 @@ mod tests {
419426 // result is reused for each format.
420427 let ( f, dir) = multi_output_config_file ( ) ;
421428 let output_dir = dir. path ( ) . join ( "dist" ) ;
422- let result = run ( f. path ( ) . to_str ( ) . unwrap ( ) , true ) ;
429+ let result = run ( f. path ( ) . to_str ( ) . unwrap ( ) , true , None ) ;
423430 assert ! ( result. is_ok( ) , "dry-run with multiple outputs should succeed: {:?}" , result) ;
424431 // No output directory should have been created in dry-run mode.
425432 assert ! ( !output_dir. exists( ) , "output directory must not be created in dry-run mode" ) ;
@@ -435,8 +442,8 @@ mod tests {
435442 let ( single_f, _single_dir) = valid_config_file ( ) ;
436443 let ( multi_f, _multi_dir) = multi_output_config_file ( ) ;
437444
438- let single_result = run ( single_f. path ( ) . to_str ( ) . unwrap ( ) , true ) ;
439- let multi_result = run ( multi_f. path ( ) . to_str ( ) . unwrap ( ) , true ) ;
445+ let single_result = run ( single_f. path ( ) . to_str ( ) . unwrap ( ) , true , None ) ;
446+ let multi_result = run ( multi_f. path ( ) . to_str ( ) . unwrap ( ) , true , None ) ;
440447
441448 assert ! ( single_result. is_ok( ) , "single-output dry-run failed: {:?}" , single_result) ;
442449 assert ! ( multi_result. is_ok( ) , "multi-output dry-run failed: {:?}" , multi_result) ;
@@ -463,7 +470,7 @@ mod tests {
463470 let ( f, dir) = valid_config_file ( ) ;
464471 let output_dir = dir. path ( ) . join ( "dist" ) ;
465472 // No cache file exists — this is a fresh state.
466- let result = run ( f. path ( ) . to_str ( ) . unwrap ( ) , true ) ;
473+ let result = run ( f. path ( ) . to_str ( ) . unwrap ( ) , true , None ) ;
467474 assert ! ( result. is_ok( ) , "dry-run should succeed without a cache: {:?}" , result) ;
468475 // In dry-run mode the output directory is never created.
469476 assert ! ( !output_dir. exists( ) , "output directory must not be created in dry-run mode" ) ;
@@ -496,7 +503,7 @@ mod tests {
496503 f. write_all ( config_content. as_bytes ( ) ) . expect ( "failed to write config" ) ;
497504
498505 // The dry-run should succeed; cache hit is detected in both modes.
499- let result = run ( f. path ( ) . to_str ( ) . unwrap ( ) , true ) ;
506+ let result = run ( f. path ( ) . to_str ( ) . unwrap ( ) , true , None ) ;
500507 assert ! ( result. is_ok( ) , "dry-run with cache hit should succeed: {:?}" , result) ;
501508 }
502509
@@ -529,7 +536,7 @@ mod tests {
529536 f. write_all ( config_content. as_bytes ( ) ) . expect ( "failed to write config" ) ;
530537
531538 // Dry-run still succeeds; it runs transforms because the hash misses.
532- let result = run ( f. path ( ) . to_str ( ) . unwrap ( ) , true ) ;
539+ let result = run ( f. path ( ) . to_str ( ) . unwrap ( ) , true , None ) ;
533540 assert ! ( result. is_ok( ) , "dry-run with cache miss should still succeed: {:?}" , result) ;
534541 }
535542
@@ -550,7 +557,7 @@ mod tests {
550557 let mut f = NamedTempFile :: new ( ) . expect ( "failed to create temp file" ) ;
551558 f. write_all ( config_content. as_bytes ( ) ) . expect ( "failed to write config" ) ;
552559
553- run ( f. path ( ) . to_str ( ) . unwrap ( ) , false ) . expect ( "build should succeed" ) ;
560+ run ( f. path ( ) . to_str ( ) . unwrap ( ) , false , None ) . expect ( "build should succeed" ) ;
554561
555562 let cache_path = output_dir. join ( ".renderflow-cache.json" ) ;
556563 assert ! ( cache_path. exists( ) , "cache file must exist after a real build" ) ;
@@ -575,9 +582,9 @@ mod tests {
575582 f. write_all ( config_content. as_bytes ( ) ) . expect ( "failed to write config" ) ;
576583
577584 // First build — cache miss, cache written.
578- run ( f. path ( ) . to_str ( ) . unwrap ( ) , false ) . expect ( "first build should succeed" ) ;
585+ run ( f. path ( ) . to_str ( ) . unwrap ( ) , false , None ) . expect ( "first build should succeed" ) ;
579586 // Second build — cache hit.
580- run ( f. path ( ) . to_str ( ) . unwrap ( ) , false ) . expect ( "second build (cache hit) should succeed" ) ;
587+ run ( f. path ( ) . to_str ( ) . unwrap ( ) , false , None ) . expect ( "second build (cache hit) should succeed" ) ;
581588
582589 let cache_path = output_dir. join ( ".renderflow-cache.json" ) ;
583590 assert ! ( cache_path. exists( ) , "cache file must still exist after second build" ) ;
@@ -612,7 +619,7 @@ mod tests {
612619 let mut f = NamedTempFile :: new ( ) . expect ( "failed to create temp file" ) ;
613620 f. write_all ( config_content. as_bytes ( ) ) . expect ( "failed to write config" ) ;
614621
615- run ( f. path ( ) . to_str ( ) . unwrap ( ) , false ) . expect ( "build should succeed" ) ;
622+ run ( f. path ( ) . to_str ( ) . unwrap ( ) , false , None ) . expect ( "build should succeed" ) ;
616623
617624 let output_cache_path = output_dir. join ( ".renderflow-output-cache.json" ) ;
618625 assert ! ( output_cache_path. exists( ) , "output cache file must exist after a real build" ) ;
@@ -638,9 +645,9 @@ mod tests {
638645 f. write_all ( config_content. as_bytes ( ) ) . expect ( "failed to write config" ) ;
639646
640647 // First build — output cache miss, pandoc runs, cache written.
641- run ( f. path ( ) . to_str ( ) . unwrap ( ) , false ) . expect ( "first build should succeed" ) ;
648+ run ( f. path ( ) . to_str ( ) . unwrap ( ) , false , None ) . expect ( "first build should succeed" ) ;
642649 // Second build — output cache hit, pandoc skipped.
643- run ( f. path ( ) . to_str ( ) . unwrap ( ) , false ) . expect ( "second build (output cache hit) should succeed" ) ;
650+ run ( f. path ( ) . to_str ( ) . unwrap ( ) , false , None ) . expect ( "second build (output cache hit) should succeed" ) ;
644651
645652 let output_cache_path = output_dir. join ( ".renderflow-output-cache.json" ) ;
646653 assert ! ( output_cache_path. exists( ) , "output cache must still exist after second build" ) ;
@@ -664,21 +671,21 @@ mod tests {
664671 f. write_all ( config_content. as_bytes ( ) ) . expect ( "failed to write config" ) ;
665672
666673 // First build with original content.
667- run ( f. path ( ) . to_str ( ) . unwrap ( ) , false ) . expect ( "first build should succeed" ) ;
674+ run ( f. path ( ) . to_str ( ) . unwrap ( ) , false , None ) . expect ( "first build should succeed" ) ;
668675
669676 // Modify input — caches must be invalidated.
670677 fs:: write ( & input_path, "# Modified\n " ) . expect ( "failed to write updated input" ) ;
671678
672679 // Second build must succeed (re-render triggered by cache miss).
673- run ( f. path ( ) . to_str ( ) . unwrap ( ) , false ) . expect ( "second build after input change should succeed" ) ;
680+ run ( f. path ( ) . to_str ( ) . unwrap ( ) , false , None ) . expect ( "second build after input change should succeed" ) ;
674681 }
675682
676683 #[ test]
677684 fn test_output_cache_not_written_in_dry_run ( ) {
678685 // In dry-run mode the output cache file must never be created.
679686 let ( f, dir) = valid_config_file ( ) ;
680687 let output_dir = dir. path ( ) . join ( "dist" ) ;
681- run ( f. path ( ) . to_str ( ) . unwrap ( ) , true ) . expect ( "dry-run should succeed" ) ;
688+ run ( f. path ( ) . to_str ( ) . unwrap ( ) , true , None ) . expect ( "dry-run should succeed" ) ;
682689 let output_cache_path = output_dir. join ( ".renderflow-output-cache.json" ) ;
683690 assert ! (
684691 !output_cache_path. exists( ) ,
@@ -708,7 +715,7 @@ mod tests {
708715 let mut f = NamedTempFile :: new ( ) . expect ( "failed to create temp file" ) ;
709716 f. write_all ( config_content. as_bytes ( ) ) . expect ( "failed to write config" ) ;
710717
711- let result = run ( f. path ( ) . to_str ( ) . unwrap ( ) , true ) ;
718+ let result = run ( f. path ( ) . to_str ( ) . unwrap ( ) , true , None ) ;
712719 assert ! ( result. is_ok( ) , "dry-run with output cache should succeed: {:?}" , result) ;
713720 }
714721}
0 commit comments