1- use std:: ffi:: OsString ;
1+ use std:: ffi:: { OsStr , OsString } ;
22use std:: iter:: Peekable ;
3- use std:: path:: PathBuf ;
3+ use std:: path:: { Path , PathBuf } ;
44
55use regex:: Regex ;
66
77use crate :: bre:: Bre ;
88use crate :: color:: ColorWhen ;
9+ use crate :: utils:: fold_file_name_case;
910
1011#[ derive( Clone , Copy , Debug , Default , Eq , PartialEq ) ]
1112pub enum Format {
@@ -52,6 +53,10 @@ pub struct Params {
5253 /// headers (`-F` and `-p`).
5354 pub function_line_patterns : Vec < Bre > ,
5455 pub recursive : bool ,
56+ /// The name from which to start comparing the top-level directories
57+ /// (`-S`).
58+ pub starting_file : Option < OsString > ,
59+ pub ignore_file_name_case : bool ,
5560}
5661
5762/// The pattern of `-p`, matching the first line of C functions.
@@ -77,6 +82,8 @@ impl Default for Params {
7782 presume_output_tty : false ,
7883 function_line_patterns : Vec :: new ( ) ,
7984 recursive : false ,
85+ starting_file : None ,
86+ ignore_file_name_case : false ,
8087 }
8188 }
8289}
@@ -175,23 +182,21 @@ pub fn parse_params<I: Iterator<Item = OsString>>(
175182 push_function_line_pattern ( & mut params, C_FUNCTION_PATTERN ) ?;
176183 continue ;
177184 }
178- let attached_function_line = param. to_str ( ) . and_then ( |p| {
179- p. strip_prefix ( "--show-function-line=" )
180- . or_else ( || p. strip_prefix ( "-F" ) . filter ( |pattern| !pattern. is_empty ( ) ) )
181- } ) ;
182- if let Some ( pattern) = attached_function_line {
183- push_function_line_pattern ( & mut params, pattern) ?;
185+ if let Some ( argument) = option_argument ( & param, "-F" , "--show-function-line" ) {
186+ let pattern = argument_value ( argument, & param, & mut opts) ?;
187+ push_function_line_pattern ( & mut params, & pattern. to_string_lossy ( ) ) ?;
184188 continue ;
185189 }
186- if param == "-F" || param == "--show-function-line" {
187- let Some ( pattern) = next_param. map ( |p| p. to_string_lossy ( ) . into_owned ( ) ) else {
188- return Err ( format ! (
189- "option '{}' requires an argument" ,
190- param. to_string_lossy( )
191- ) ) ;
192- } ;
193- opts. next ( ) ;
194- push_function_line_pattern ( & mut params, & pattern) ?;
190+ if param == "--ignore-file-name-case" {
191+ params. ignore_file_name_case = true ;
192+ continue ;
193+ }
194+ if param == "--no-ignore-file-name-case" {
195+ params. ignore_file_name_case = false ;
196+ continue ;
197+ }
198+ if let Some ( argument) = option_argument ( & param, "-S" , "--starting-file" ) {
199+ params. starting_file = Some ( argument_value ( argument, & param, & mut opts) ?) ;
195200 continue ;
196201 }
197202 if param == "-r" || param == "--recursive" {
@@ -353,10 +358,12 @@ pub fn parse_params<I: Iterator<Item = OsString>>(
353358 let mut to_path: PathBuf = PathBuf :: from ( & params. to ) ;
354359
355360 if from_path. is_dir ( ) && to_path. is_file ( ) {
356- from_path. push ( to_path. file_name ( ) . unwrap ( ) ) ;
361+ let name = directory_entry ( & from_path, to_path. file_name ( ) . unwrap ( ) , & params) ;
362+ from_path. push ( name) ;
357363 params. from = from_path. into_os_string ( ) ;
358364 } else if from_path. is_file ( ) && to_path. is_dir ( ) {
359- to_path. push ( from_path. file_name ( ) . unwrap ( ) ) ;
365+ let name = directory_entry ( & to_path, from_path. file_name ( ) . unwrap ( ) , & params) ;
366+ to_path. push ( name) ;
360367 params. to = to_path. into_os_string ( ) ;
361368 }
362369
@@ -378,6 +385,61 @@ pub fn parse_params<I: Iterator<Item = OsString>>(
378385 Ok ( ( params, options) )
379386}
380387
388+ /// Returns the name of the entry of `dir` to compare with a file named
389+ /// `name`: `name` itself, or with --ignore-file-name-case, an entry whose name
390+ /// differs only by case when `name` does not exist.
391+ fn directory_entry ( dir : & Path , name : & OsStr , params : & Params ) -> OsString {
392+ if params. ignore_file_name_case && !dir. join ( name) . exists ( ) {
393+ let wanted = fold_file_name_case ( name) ;
394+ let entries = std:: fs:: read_dir ( dir) . into_iter ( ) . flatten ( ) . flatten ( ) ;
395+ if let Some ( entry) = entries
396+ . map ( |entry| entry. file_name ( ) )
397+ . find ( |entry| fold_file_name_case ( entry) == wanted)
398+ {
399+ return entry;
400+ }
401+ }
402+ name. to_os_string ( )
403+ }
404+
405+ /// How the argument of an option is given.
406+ enum OptionArgument < ' a > {
407+ /// In the same command line argument, as in `-Fx` or `--show-function-line=x`.
408+ Attached ( & ' a str ) ,
409+ /// As the next command line argument.
410+ Separate ,
411+ }
412+
413+ /// Returns how the argument of the option named `short` (like `-F`) or
414+ /// `long` is given, if `param` is that option.
415+ fn option_argument < ' a > ( param : & ' a OsStr , short : & str , long : & str ) -> Option < OptionArgument < ' a > > {
416+ if param == short || param == long {
417+ return Some ( OptionArgument :: Separate ) ;
418+ }
419+ let param = param. to_str ( ) ?;
420+ param
421+ . strip_prefix ( long)
422+ . and_then ( |rest| rest. strip_prefix ( '=' ) )
423+ . or_else ( || param. strip_prefix ( short) . filter ( |value| !value. is_empty ( ) ) )
424+ . map ( OptionArgument :: Attached )
425+ }
426+
427+ /// Returns the value of the argument of the option `param`, taking the next
428+ /// command line argument when it is not attached.
429+ fn argument_value (
430+ argument : OptionArgument ,
431+ param : & OsStr ,
432+ opts : & mut impl Iterator < Item = ( usize , OsString ) > ,
433+ ) -> Result < OsString , String > {
434+ match argument {
435+ OptionArgument :: Attached ( value) => Ok ( value. into ( ) ) ,
436+ OptionArgument :: Separate => opts
437+ . next ( )
438+ . map ( |( _, value) | value)
439+ . ok_or_else ( || format ! ( "option '{}' requires an argument" , param. to_string_lossy( ) ) ) ,
440+ }
441+ }
442+
381443/// Validates and records a `-F` pattern.
382444fn push_function_line_pattern ( params : & mut Params , pattern : & str ) -> Result < ( ) , String > {
383445 let bre = Bre :: new ( pattern) . map_err ( |error| format ! ( "'{pattern}': {error}" ) ) ?;
@@ -1208,4 +1270,51 @@ mod tests {
12081270 ) ;
12091271 assert_eq ! ( options( & [ "diff" , "-" , "new" , "-t" ] ) , [ os( "-t" ) ] ) ;
12101272 }
1273+
1274+ #[ test]
1275+ fn starting_file ( ) {
1276+ for args in [
1277+ & [ "-S" , "mid" ] [ ..] ,
1278+ & [ "-Smid" ] ,
1279+ & [ "--starting-file=mid" ] ,
1280+ & [ "--starting-file" , "mid" ] ,
1281+ ] {
1282+ let args = std:: iter:: once ( "diff" )
1283+ . chain ( args. iter ( ) . copied ( ) )
1284+ . chain ( [ "foo" , "bar" ] ) ;
1285+ let params = parse_params ( args. map ( os) . peekable ( ) ) . unwrap ( ) ;
1286+ assert_eq ! ( params. starting_file, Some ( os( "mid" ) ) ) ;
1287+ assert_eq ! ( params. from, os( "foo" ) ) ;
1288+ }
1289+ assert ! ( parse_params(
1290+ [ os( "diff" ) , os( "foo" ) , os( "bar" ) , os( "-S" ) ]
1291+ . into_iter( )
1292+ . peekable( )
1293+ )
1294+ . is_err( ) ) ;
1295+ }
1296+ #[ test]
1297+ fn ignore_file_name_case ( ) {
1298+ // The last of --ignore-file-name-case and --no-ignore-file-name-case
1299+ // wins.
1300+ for ( args, expected) in [
1301+ ( & [ ] [ ..] , false ) ,
1302+ ( & [ "--ignore-file-name-case" ] , true ) ,
1303+ ( & [ "--no-ignore-file-name-case" ] , false ) ,
1304+ (
1305+ & [ "--ignore-file-name-case" , "--no-ignore-file-name-case" ] ,
1306+ false ,
1307+ ) ,
1308+ (
1309+ & [ "--no-ignore-file-name-case" , "--ignore-file-name-case" ] ,
1310+ true ,
1311+ ) ,
1312+ ] {
1313+ let args = std:: iter:: once ( "diff" )
1314+ . chain ( args. iter ( ) . copied ( ) )
1315+ . chain ( [ "foo" , "bar" ] ) ;
1316+ let params = parse_params ( args. map ( os) . peekable ( ) ) . unwrap ( ) ;
1317+ assert_eq ! ( params. ignore_file_name_case, expected) ;
1318+ }
1319+ }
12111320}
0 commit comments