@@ -106,6 +106,23 @@ fn should_process_file(entry: &ignore::DirEntry, args: &Cli, base_path: &Path) -
106106 return false ;
107107 }
108108
109+ // Handle replacement mode with --only-include
110+ if let Some ( ref only_includes) = args. only_include {
111+ let matches_only_include = matches_include_patterns ( path, only_includes, base_path) ;
112+
113+ if !matches_only_include {
114+ return false ;
115+ }
116+
117+ // Apply excludes if any
118+ if let Some ( ref excludes) = args. exclude {
119+ return !matches_exclude_patterns ( path, excludes, base_path) ;
120+ }
121+
122+ return true ;
123+ }
124+
125+ // Handle additive mode
109126 // Check if it's a source file
110127 let is_source = source_detection:: is_source_file ( path) ;
111128
@@ -357,6 +374,7 @@ mod tests {
357374 paths : vec ! [ dir_path. to_string_lossy( ) . to_string( ) ] ,
358375 config_path : false ,
359376 include : None ,
377+ only_include : None ,
360378 exclude : None ,
361379 max_size : Some ( 10 * 1024 * 1024 ) , // 10MB
362380 max_depth : Some ( 10 ) ,
@@ -878,4 +896,167 @@ mod tests {
878896
879897 Ok ( ( ) )
880898 }
899+
900+ #[ test]
901+ fn test_only_include_replacement_behavior ( ) -> Result < ( ) > {
902+ let ( dir, _files) = setup_test_directory ( ) ?;
903+
904+ // Create various test files including non-source files
905+ fs:: write ( dir. path ( ) . join ( "config.conf" ) , "key=value" ) ?;
906+ fs:: write ( dir. path ( ) . join ( "data.toml" ) , "[section]\n key = 'value'" ) ?;
907+ fs:: write ( dir. path ( ) . join ( "template.peb" ) , "template content" ) ?;
908+
909+ let mut cli = create_test_cli ( dir. path ( ) ) ;
910+
911+ // Test 1: --only-include should ONLY include specified patterns, no other files
912+ cli. only_include = Some ( vec ! [ "*.conf" . to_string( ) ] ) ;
913+ let entries = process_entries ( & cli) ?;
914+
915+ assert_eq ! ( entries. len( ) , 1 ) ;
916+ assert ! ( entries[ 0 ] . path. extension( ) . and_then( |ext| ext. to_str( ) ) == Some ( "conf" ) ) ;
917+
918+ // Verify no other files are included
919+ let extensions: Vec < _ > = entries
920+ . iter ( )
921+ . filter_map ( |e| e. path . extension ( ) . and_then ( |ext| ext. to_str ( ) ) )
922+ . collect ( ) ;
923+ assert ! ( !extensions. contains( & "rs" ) ) ;
924+ assert ! ( !extensions. contains( & "py" ) ) ;
925+ assert ! ( !extensions. contains( & "md" ) ) ;
926+ assert ! ( !extensions. contains( & "toml" ) ) ;
927+
928+ // Test 2: Multiple patterns in --only-include
929+ cli. only_include = Some ( vec ! [ "*.conf" . to_string( ) , "*.toml" . to_string( ) ] ) ;
930+ let entries = process_entries ( & cli) ?;
931+
932+ assert_eq ! ( entries. len( ) , 2 ) ;
933+ let extensions: Vec < _ > = entries
934+ . iter ( )
935+ . filter_map ( |e| e. path . extension ( ) . and_then ( |ext| ext. to_str ( ) ) )
936+ . collect ( ) ;
937+ assert ! ( extensions. contains( & "conf" ) ) ;
938+ assert ! ( extensions. contains( & "toml" ) ) ;
939+ assert ! ( !extensions. contains( & "rs" ) ) ; // No other files
940+ assert ! ( !extensions. contains( & "py" ) ) ;
941+
942+ // Test 3: --only-include with exclude patterns
943+ cli. only_include = Some ( vec ! [
944+ "*.conf" . to_string( ) ,
945+ "*.toml" . to_string( ) ,
946+ "*.peb" . to_string( ) ,
947+ ] ) ;
948+ cli. exclude = Some ( vec ! [ Exclude :: Pattern ( "*.toml" . to_string( ) ) ] ) ;
949+ let entries = process_entries ( & cli) ?;
950+
951+ assert_eq ! ( entries. len( ) , 2 ) ; // conf and peb, but not toml (excluded)
952+ let extensions: Vec < _ > = entries
953+ . iter ( )
954+ . filter_map ( |e| e. path . extension ( ) . and_then ( |ext| ext. to_str ( ) ) )
955+ . collect ( ) ;
956+ assert ! ( extensions. contains( & "conf" ) ) ;
957+ assert ! ( extensions. contains( & "peb" ) ) ;
958+ assert ! ( !extensions. contains( & "toml" ) ) ; // Excluded
959+ assert ! ( !extensions. contains( & "rs" ) ) ; // No other files
960+
961+ // Test 4: --only-include with pattern that matches nothing
962+ cli. only_include = Some ( vec ! [ "*.nonexistent" . to_string( ) ] ) ;
963+ cli. exclude = None ;
964+ let entries = process_entries ( & cli) ?;
965+
966+ assert_eq ! ( entries. len( ) , 0 ) ; // Should match nothing
967+
968+ Ok ( ( ) )
969+ }
970+
971+ #[ test]
972+ fn test_only_include_vs_include_behavior_difference ( ) -> Result < ( ) > {
973+ let ( dir, _files) = setup_test_directory ( ) ?;
974+
975+ // Create a non-source file
976+ fs:: write ( dir. path ( ) . join ( "config.conf" ) , "key=value" ) ?;
977+
978+ let mut cli = create_test_cli ( dir. path ( ) ) ;
979+
980+ // Test additive behavior with --include
981+ cli. include = Some ( vec ! [ "*.conf" . to_string( ) ] ) ;
982+ cli. only_include = None ;
983+ let additive_entries = process_entries ( & cli) ?;
984+
985+ // Should include conf + source files
986+ let additive_extensions: Vec < _ > = additive_entries
987+ . iter ( )
988+ . filter_map ( |e| e. path . extension ( ) . and_then ( |ext| ext. to_str ( ) ) )
989+ . collect ( ) ;
990+ assert ! ( additive_extensions. contains( & "conf" ) ) ; // Additional pattern
991+ assert ! ( additive_extensions. contains( & "rs" ) ) ; // Source files
992+ assert ! ( additive_extensions. contains( & "py" ) ) ; // Source files
993+ assert ! ( additive_extensions. contains( & "md" ) ) ; // Source files
994+
995+ // Test replacement behavior with --only-include
996+ cli. include = None ;
997+ cli. only_include = Some ( vec ! [ "*.conf" . to_string( ) ] ) ;
998+ let replacement_entries = process_entries ( & cli) ?;
999+
1000+ // Should include ONLY conf files
1001+ assert_eq ! ( replacement_entries. len( ) , 1 ) ;
1002+ assert ! (
1003+ replacement_entries[ 0 ]
1004+ . path
1005+ . extension( )
1006+ . and_then( |ext| ext. to_str( ) )
1007+ == Some ( "conf" )
1008+ ) ;
1009+
1010+ let replacement_extensions: Vec < _ > = replacement_entries
1011+ . iter ( )
1012+ . filter_map ( |e| e. path . extension ( ) . and_then ( |ext| ext. to_str ( ) ) )
1013+ . collect ( ) ;
1014+ assert ! ( replacement_extensions. contains( & "conf" ) ) ; // Only pattern
1015+ assert ! ( !replacement_extensions. contains( & "rs" ) ) ; // No source files
1016+ assert ! ( !replacement_extensions. contains( & "py" ) ) ; // No source files
1017+ assert ! ( !replacement_extensions. contains( & "md" ) ) ; // No source files
1018+
1019+ // Verify the counts are different
1020+ assert ! ( additive_entries. len( ) > replacement_entries. len( ) ) ;
1021+
1022+ Ok ( ( ) )
1023+ }
1024+
1025+ #[ test]
1026+ fn test_only_include_single_file_processing ( ) -> Result < ( ) > {
1027+ let ( dir, _files) = setup_test_directory ( ) ?;
1028+
1029+ // Create and test single file processing with a truly non-source file
1030+ let config_path = dir. path ( ) . join ( "config.conf" ) ;
1031+ fs:: write ( & config_path, "key=value" ) ?;
1032+
1033+ let mut cli = create_test_cli ( & config_path) ;
1034+ cli. paths = vec ! [ config_path. to_string_lossy( ) . to_string( ) ] ;
1035+
1036+ // Test 1: Single non-source file without --only-include should be rejected
1037+ cli. only_include = None ;
1038+ let entries = process_entries ( & cli) ?;
1039+ assert_eq ! ( entries. len( ) , 0 ) ;
1040+
1041+ // Test 2: Single non-source file WITH --only-include should be accepted
1042+ cli. only_include = Some ( vec ! [ "*.conf" . to_string( ) ] ) ;
1043+ let entries = process_entries ( & cli) ?;
1044+ assert_eq ! ( entries. len( ) , 1 ) ;
1045+ assert ! ( entries[ 0 ] . path. extension( ) . and_then( |ext| ext. to_str( ) ) == Some ( "conf" ) ) ;
1046+
1047+ // Test 3: Single source file WITH --only-include that doesn't match should be rejected
1048+ let rs_path = dir. path ( ) . join ( "src/main.rs" ) ;
1049+ cli. paths = vec ! [ rs_path. to_string_lossy( ) . to_string( ) ] ;
1050+ cli. only_include = Some ( vec ! [ "*.conf" . to_string( ) ] ) ;
1051+ let entries = process_entries ( & cli) ?;
1052+ assert_eq ! ( entries. len( ) , 0 ) ;
1053+
1054+ // Test 4: Single source file WITH --only-include that matches should be accepted
1055+ cli. only_include = Some ( vec ! [ "*.rs" . to_string( ) ] ) ;
1056+ let entries = process_entries ( & cli) ?;
1057+ assert_eq ! ( entries. len( ) , 1 ) ;
1058+ assert ! ( entries[ 0 ] . path. extension( ) . and_then( |ext| ext. to_str( ) ) == Some ( "rs" ) ) ;
1059+
1060+ Ok ( ( ) )
1061+ }
8811062}
0 commit comments