@@ -28,6 +28,8 @@ enum CopyType {
2828 SingleFile ,
2929 /// equivalent to cp -a <source> <dest>
3030 Archive ,
31+ /// equivalent to cp -r <source> <dest>
32+ Recursive ,
3133}
3234
3335/// Encapsulate a copy operation
@@ -42,18 +44,22 @@ struct CopyOperation {
4244
4345/// Parse command line arguments and transform into `CopyOperation`
4446fn parse_args ( args : Vec < & str > ) -> io:: Result < CopyOperation > {
45- if !( args. len ( ) == 3 || args. len ( ) == 4 && args[ 1 ] . eq ( "-a" ) ) {
47+ if !( args. len ( ) == 3 || ( args. len ( ) == 4 && ( args[ 1 ] == "-a" || args [ 1 ] == "-r" ) ) ) {
4648 return Err ( io:: Error :: new (
4749 io:: ErrorKind :: InvalidInput ,
48- "Invalid parameters. Expected cp [-a] <source> <destination>" ,
50+ "Invalid parameters. Expected cp [-a | -r ] <source> <destination>" ,
4951 ) ) ;
5052 }
5153
5254 if args. len ( ) == 4 {
5355 return Ok ( CopyOperation {
5456 source : PathBuf :: from ( args[ 2 ] ) ,
5557 destination : PathBuf :: from ( args[ 3 ] ) ,
56- copy_type : CopyType :: Archive ,
58+ copy_type : match args[ 1 ] {
59+ "-a" => CopyType :: Archive ,
60+ "-r" => CopyType :: Recursive ,
61+ _ => panic ! ( "Invalid option. Expected -a or -r" ) ,
62+ } ,
5763 } ) ;
5864 }
5965
@@ -69,10 +75,40 @@ fn do_copy(operation: CopyOperation) -> io::Result<()> {
6975 match operation. copy_type {
7076 CopyType :: Archive => copy_archive ( & operation. source , & operation. destination ) ?,
7177 CopyType :: SingleFile => fs:: copy ( & operation. source , & operation. destination ) . map ( |_| ( ) ) ?,
78+ CopyType :: Recursive => copy_recursive ( & operation. source , & operation. destination ) ?,
7279 } ;
7380 Ok ( ( ) )
7481}
7582
83+ fn copy_recursive ( source : & Path , dest : & Path ) -> io:: Result < ( ) > {
84+ let mut stack = VecDeque :: new ( ) ;
85+ stack. push_back ( ( source. to_path_buf ( ) , dest. to_path_buf ( ) ) ) ;
86+ while let Some ( ( current_source, current_dest) ) = stack. pop_back ( ) {
87+ if current_source. is_dir ( ) {
88+ if !current_dest. exists ( ) {
89+ fs:: create_dir ( & current_dest) ?;
90+ }
91+ for entry in fs:: read_dir ( current_source) ? {
92+ let next_source = entry?. path ( ) ;
93+ let next_dest =
94+ current_dest
95+ . clone ( )
96+ . join ( next_source. file_name ( ) . ok_or ( io:: Error :: new (
97+ io:: ErrorKind :: InvalidInput ,
98+ "Invalid source file" ,
99+ ) ) ?) ;
100+ stack. push_back ( ( next_source, next_dest) ) ;
101+ }
102+ } else if current_source. is_symlink ( ) {
103+ // Follow symbolic links as regular files
104+ fs:: copy ( current_source, current_dest) ?;
105+ } else if current_source. is_file ( ) {
106+ fs:: copy ( current_source, current_dest) ?;
107+ }
108+ }
109+ Ok ( ( ) )
110+ }
111+
76112// Execute the recursive type of copy operation
77113fn copy_archive ( source : & Path , dest : & Path ) -> io:: Result < ( ) > {
78114 let mut stack = VecDeque :: new ( ) ;
@@ -100,7 +136,6 @@ fn copy_archive(source: &Path, dest: &Path) -> io::Result<()> {
100136 fs:: copy ( current_source, current_dest) ?;
101137 }
102138 }
103-
104139 Ok ( ( ) )
105140}
106141
@@ -163,7 +198,7 @@ mod tests {
163198 fn parser_failure ( ) {
164199 // prepare
165200 let inputs = vec ! [
166- vec![ "cp" , "-r" , "foo.txt" , "bar.txt" ] ,
201+ vec![ "cp" , "-r" , "foo.txt" , "bar.txt" , "foo1.txt" ] ,
167202 vec![ "cp" , "-a" , "param1" , "param2" , "param3" ] ,
168203 vec![ "cp" , "param1" , "param2" , "param3" ] ,
169204 ] ;
@@ -177,6 +212,24 @@ mod tests {
177212 }
178213 }
179214
215+ #[ test]
216+ fn parser_correct ( ) {
217+ // prepare
218+ let inputs = vec ! [
219+ vec![ "cp" , "-r" , "foo.txt" , "bar.txt" ] ,
220+ vec![ "cp" , "-a" , "param1" , "param2" ] ,
221+ vec![ "cp" , "param1" , "param2" ] ,
222+ ] ;
223+
224+ for input in inputs. into_iter ( ) {
225+ // act
226+ let result = parse_args ( input. clone ( ) ) ;
227+
228+ // assert
229+ assert ! ( result. is_ok( ) , "input should fail {:?}" , input) ;
230+ }
231+ }
232+
180233 #[ test]
181234 fn test_copy_single ( ) {
182235 // prepare
@@ -220,6 +273,51 @@ mod tests {
220273 assert ! ( result. is_err( ) ) ;
221274 }
222275
276+ #[ test]
277+ fn test_copy_recursive ( ) {
278+ // prepare
279+ let tempdir = tempfile:: tempdir ( ) . unwrap ( ) ;
280+ let test_base = tempdir. path ( ) . to_path_buf ( ) ;
281+ [ "foo" , "foo/foo0" , "foo/foo1" , "foo/bar" ]
282+ . iter ( )
283+ . for_each ( |x| create_dir ( & test_base, x) ) ;
284+ let files = [
285+ "foo/file1.txt" ,
286+ "foo/file2.txt" ,
287+ "foo/foo1/file3.txt" ,
288+ "foo/bar/file4.txt" ,
289+ ] ;
290+ files. iter ( ) . for_each ( |x| create_file ( & test_base, x) ) ;
291+ [ ( "foo/symlink1.txt" , "./file1.txt" ) ]
292+ . iter ( )
293+ . for_each ( |( x, y) | create_symlink ( & test_base, x, y) ) ;
294+
295+ // act
296+ let recursive_copy = CopyOperation {
297+ copy_type : CopyType :: Recursive ,
298+ source : test_base. join ( "foo" ) ,
299+ destination : test_base. join ( "bar" ) ,
300+ } ;
301+ do_copy ( recursive_copy) . unwrap ( ) ;
302+
303+ // assert
304+ files. iter ( ) . for_each ( |x| {
305+ assert_same_file (
306+ & test_base. join ( x) ,
307+ & test_base. join ( x. replace ( "foo/" , "bar/" ) ) ,
308+ )
309+ } ) ;
310+ assert_same_file (
311+ & test_base. join ( "foo/symlink1.txt" ) ,
312+ & test_base. join ( "bar/symlink1.txt" ) ,
313+ ) ;
314+ // recursive copy will treat symlink as a file
315+ assert_recursive_same_link (
316+ & test_base. join ( "foo/symlink1.txt" ) ,
317+ & test_base. join ( "bar/symlink1.txt" ) ,
318+ )
319+ }
320+
223321 #[ test]
224322 fn test_copy_archive ( ) {
225323 // prepare
@@ -342,4 +440,16 @@ mod tests {
342440
343441 assert_eq ! ( fs:: read_link( source) . unwrap( ) , fs:: read_link( dest) . unwrap( ) ) ;
344442 }
443+
444+ fn assert_recursive_same_link ( source : & Path , dest : & Path ) {
445+ assert ! ( source. exists( ) ) ;
446+ assert ! ( dest. exists( ) ) ;
447+ assert ! ( source. is_symlink( ) ) ;
448+ assert ! ( dest. is_file( ) ) ;
449+
450+ assert_eq ! (
451+ fs:: read_to_string( source) . unwrap( ) ,
452+ fs:: read_to_string( dest) . unwrap( )
453+ ) ;
454+ }
345455}
0 commit comments