1 =begin
2
3 = fileutils.rb
4
5 Copyright (c) 2000-2002 Minero Aoki <aamine@loveruby.net>
6
7 This program is free software.
8 You can distribute/modify this program under the same terms of ruby.
9
10 == module FileUtils
11
12 The module which implements basic file operations.
13
14 === Module Functions
15
16 --- FileUtils.cd( dir, *options )
17 --- FileUtils.cd( dir, *options ) {|dir| .... }
18 Options: noop verbose
19
20 changes the current directory to the directory DIR.
21
22 If this method is called with block, resumes to the old
23 working directory after the block execution finished.
24
25 FileUtils.cd '/', :verbose # chdir and report it
26
27 --- FileUtils.uptodate?( newer, older_list, *options )
28 Options: verbose
29
30 returns true if NEWER is newer than all OLDER_LIST.
31 Non-exist files are older than any file.
32
33 FileUtils.newest? 'hello.o', 'hello.c', 'hello.h' or system 'make'
34
35 --- FileUtils.mkdir( dir, *options )
36 Options: noop verbose
37
38 creates directorie(s) DIR.
39
40 FileUtils.mkdir 'test'
41 FileUtils.mkdir %w( tmp data )
42 FileUtils.mkdir 'notexist', :noop # does not create really
43
44 --- FileUtils.mkdir_p( dir, *options )
45 Options: noop verbose
46
47 makes dirctories DIR and all its parent directories.
48 For example,
49
50 FileUtils.mkdir_p '/usr/local/bin/ruby'
51
52 causes to make following directories (if it does not exist).
53 * /usr
54 * /usr/local
55 * /usr/local/bin
56 * /usr/local/bin/ruby
57
58 --- FileUtils.rmdir( dir, *options )
59 Options: noop, verbose
60
61 removes directory DIR.
62
63 FileUtils.rmdir 'somedir'
64 FileUtils.rmdir %w(somedir anydir otherdir)
65 # does not remove directory really, outputing message.
66 FileUtils.rmdir 'somedir', :verbose, :noop
67
68 --- FileUtils.ln( old, new, *options )
69 Options: force noop verbose
70
71 creates a hard link NEW which points OLD.
72 If NEW already exists and it is a directory, creates a symbolic link NEW/OLD.
73 If NEW already exists and it is not a directory, raises Errno::EEXIST.
74 But if :force option is set, overwrite NEW.
75
76 FileUtils.ln 'gcc', 'cc', :verbose
77 FileUtils.ln '/usr/bin/emacs21', '/usr/bin/emacs'
78
79 --- FileUtils.ln( list, destdir, *options )
80 Options: force noop verbose
81
82 creates hard links DESTDIR/LIST[0], DESTDIR/LIST[1], DESTDIR/LIST[2], ...
83 And each link points LIST[0], LIST[1], LIST[2], ...
84 If DESTDIR is not a directory, raises Errno::ENOTDIR.
85
86 include FileUtils
87 cd '/bin'
88 ln %w(cp mv mkdir), '/usr/bin'
89
90 --- FileUtils.ln_s( old, new, *options )
91 Options: force noop verbose
92
93 creates a symbolic link NEW which points OLD.
94 If NEW already exists and it is a directory, creates a symbolic link NEW/OLD.
95 If NEW already exists and it is not a directory, raises Errno::EEXIST.
96 But if :force option is set, overwrite NEW.
97
98 FileUtils.ln_s '/usr/bin/ruby', '/usr/local/bin/ruby'
99 FileUtils.ln_s 'verylongsourcefilename.c', 'c', :force
100
101 --- FileUtils.ln_s( list, destdir, *options )
102 Options: force noop verbose
103
104 creates symbolic link dir/file1, dir/file2 ... which point to
105 file1, file2 ... If DIR is not a directory, raises Errno::ENOTDIR.
106 If last argument is a directory, links DIR/LIST[0] to LIST[0],
107 DIR/LIST[1] to LIST[1], ....
108 creates symbolic links DESTDIR/LIST[0] which points LIST[0].
109 DESTDIR/LIST[1] to LIST[1], ....
110 If DESTDIR is not a directory, raises Errno::ENOTDIR.
111
112 FileUtils.ln_s Dir.glob('bin/*.rb'), '/home/aamine/bin'
113
114 --- FileUtils.ln_sf( src, dest, *options )
115 Options: noop verbose
116
117 same to ln_s(src,dest,:force)
118
119 --- FileUtils.cp( src, dest, *options )
120 Options: preserve noop verbose
121
122 copies a file SRC to DEST. If DEST is a directory, copies
123 SRC to DEST/SRC.
124
125 FileUtils.cp 'eval.c', 'eval.c.org'
126
127 --- FileUtils.cp( list, dir, *options )
128 Options: preserve noop verbose
129
130 copies FILE1 to DIR/FILE1, FILE2 to DIR/FILE2 ...
131
132 FileUtils.cp 'cgi.rb', 'complex.rb', 'date.rb', '/usr/lib/ruby/1.6'
133 FileUtils.cp :verbose, %w(cgi.rb complex.rb date.rb), '/usr/lib/ruby/1.6'
134
135 --- FileUtils.cp_r( src, dest, *options )
136 Options: preserve noop verbose
137
138 copies SRC to DEST. If SRC is a directory, this method copies
139 its all contents recursively. If DEST is a directory, copies
140 SRC to DEST/SRC.
141
142 # installing ruby library "mylib" under the site_ruby
143 FileUtils.rm_r site_ruby + '/mylib', :force
144 FileUtils.cp_r 'lib/', site_ruby + '/mylib'
145
146 --- FileUtils.cp_r( list, dir, *options )
147 Options: preserve noop verbose
148
149 copies a file or a directory LIST[0] to DIR/LIST[0], LIST[1] to DIR/LIST[1], ...
150 If LIST[n] is a directory, copies its contents recursively.
151
152 FileUtils.cp_r %w(mail.rb field.rb debug/) site_ruby + '/tmail'
153 FileUtils.cp_r Dir.glob('*.rb'), '/home/aamine/lib/ruby', :noop, :verbose
154
155 --- FileUtils.mv( src, dest, *options )
156 Options: noop verbose
157
158 moves a file SRC to DEST.
159 If FILE and DEST exist on the different disk partition,
160 copies it.
161
162 FileUtils.mv 'badname.rb', 'goodname.rb'
163 FileUtils.mv 'stuff.rb', 'lib/ruby', :force
164
165 --- FileUtils.mv( list, dir, *options )
166 Options: noop verbose
167
168 moves FILE1 to DIR/FILE1, FILE2 to DIR/FILE2 ...
169 If FILE and DEST exist on the different disk partition,
170 copies it.
171
172 FileUtils.mv 'junk.txt', 'dust.txt', '/home/aamine/.trash/'
173 FileUtils.mv Dir.glob('test*.rb'), 'T', :noop, :verbose
174
175 --- FileUtils.rm( list, *options )
176 Options: force noop verbose
177
178 remove files LIST[0], LIST[1]... This method cannot remove directory.
179 This method ignores all errors when :force option is set.
180
181 FileUtils.rm %w( junk.txt dust.txt )
182 FileUtils.rm Dir['*.so']
183 FileUtils.rm 'NotExistFile', :force # never raises exception
184
185 --- FileUtils.rm_r( list, *options )
186 Options: force noop verbose
187
188 remove files LIST[0] LIST[1]... If LIST[n] is a directory,
189 removes its all contents recursively. This method ignores
190 StandardError when :force option is set.
191
192 FileUtils.rm_r Dir.glob('/tmp/*')
193 FileUtils.rm_r '/', :force # :-)
194
195 --- FileUtils.rm_rf( list, *options )
196 Options: noop verbose
197
198 same to rm_r(list,:force)
199
200 --- FileUtils.cmp( file_a, file_b, *options )
201 Options: verbose
202
203 returns true if contents of a file A and a file B is identical.
204
205 FileUtils.cmp 'somefile', 'somefile' #=> true
206 FileUtils.cmp '/bin/cp', '/bin/mv' #=> maybe false.
207
208 --- FileUtils.install( src, dest, mode = <src's>, *options )
209 Options: noop verbose
210
211 If SRC is not same to DEST, copies it and changes the permittion
212 mode to MODE.
213
214 FileUtils.install 'ruby', '/usr/local/bin/ruby', 0755, :verbose
215 FileUtils.install 'lib.rb', '/usr/local/lib/ruby/site_ruby', :verbose
216
217 --- FileUtils.chmod( mode, list, *options )
218 Options: noop verbose
219
220 changes permittion bits on the named FILEs to the bit pattern
221 represented by MODE.
222
223 FileUtils.chmod 0644, 'my.rb', 'your.rb'
224 FileUtils.chmod 0755, 'somecommand'
225 FileUtils.chmod 0755, '/usr/bin/ruby', :verbose
226
227 --- FileUtils.touch( list, *options )
228 Options: noop verbose
229
230 updates modification time (mtime) and access time (atime) of
231 LIST[0], LIST[1]...
232 If LIST[n] does not exist, creates an empty file.
233
234 FileUtils.touch 'timestamp'
235 FileUtils.touch Dir.glob('*.c'); system 'make'
236
237 == module FileUtils::Verbose
238
239 This class has all methods of FileUtils module and it works as
240 same, but outputs messages before action. You can also pass
241 verbose flag to all methods.
242
243 == module FileUtils::NoWrite
244
245 This class has all methods of FileUtils module,
246 but never changes files/directories.
247
248 =end
249
250
251 module FileUtils
252
253 # all methods are module_function.
254
255 def cd( dir, *options, &block )
256 noop, verbose, = fu_parseargs(options, :noop, :verbose)
257 fu_output_message "cd #{dir}" if verbose
258 Dir.chdir dir, &block unless noop
259 fu_output_message 'cd -' if verbose and block
260 end
261
262 alias chdir cd
263
264
265 def uptodate?( new, *args )
266 verbose, = fu_parseargs(args, :verbose)
267 fu_output_message "newest? #{args.join ' '}" if verbose
268
269 return false unless FileTest.exist? new
270 new_time = File.ctime(new)
271 args.each do |old|
272 if FileTest.exist? old then
273 return false unless new_time > File.mtime(old)
274 end
275 end
276 true
277 end
278
279
280 def mkdir( list, *options )
281 noop, verbose, = fu_parseargs(options, :noop, :verbose)
282 list = fu_list(list)
283 fu_output_message "mkdir #{list.join ' '}" if verbose
284 return if noop
285
286 list.each do |dir|
287 Dir.mkdir dir
288 end
289 end
290
291 def mkdir_p( list, *options )
292 noop, verbose, = fu_parseargs(options, :noop, :verbose)
293 list = fu_list(list)
294 fu_output_message "mkdir -p #{list.join ' '}" if verbose
295 return *list if noop
296
297 list.collect {|n| File.expand_path(n) }.each do |dir|
298 stack = []
299 until FileTest.directory? dir do
300 stack.push dir
301 dir = File.dirname(dir)
302 end
303 stack.reverse_each do |dir|
304 Dir.mkdir dir
305 end
306 end
307
308 return *list
309 end
310
311 alias mkpath mkdir_p
312 alias makedirs mkdir_p
313
314
315 def rmdir( list, *options )
316 noop, verbose, = fu_parseargs(options, :noop, :verbose)
317 list = fu_list(list)
318 fu_output_message "rmdir #{list.join ' '}" if verbose
319 return if noop
320
321 list.each do |dir|
322 Dir.rmdir dir
323 end
324 end
325
326
327 def ln( src, dest, *options )
328 force, noop, verbose, = fu_parseargs(options, :force, :noop, :verbose)
329 fu_output_message "ln #{argv.join ' '}" if verbose
330 return if noop
331
332 fu_each_src_dest( src, dest ) do |s,d|
333 remove_file d, true if force
334 File.link s, d
335 end
336 end
337
338 alias link ln
339
340 def ln_s( src, dest, *options )
341 force, noop, verbose, = fu_parseargs(options, :force, :noop, :verbose)
342 fu_output_message "ln -s#{force ? 'f' : ''} #{[src,dest].flatten.join ' '}" if verbose
343 return if noop
344
345 fu_each_src_dest( src, dest ) do |s,d|
346 remove_file d, true if force
347 File.symlink s, d
348 end
349 end
350
351 alias symlink ln_s
352
353 def ln_sf( src, dest, *options )
354 noop, verbose, = fu_parseargs(options, :noop, :verbose)
355 ln_s src, dest, :force, *options
356 end
357
358
359 def cp( src, dest, *options )
360 preserve, noop, verbose, = fu_parseargs(options, :preserve, :noop, :verbose)
361 fu_output_message "cp#{preserve ? ' -p' : ''} #{[src,dest].flatten.join ' '}" if verbose
362 return if noop
363
364 fu_each_src_dest( src, dest ) do |s,d|
365 fu_preserve_attr(preserve, s, d) {
366 copy_file s, d
367 }
368 end
369 end
370
371 alias copy cp
372
373 def cp_r( src, dest, *options )
374 preserve, noop, verbose, = fu_parseargs(options, :preserve, :noop, :verbose)
375 fu_output_message "cp -r #{[src,dest].flatten.join ' '}" if verbose
376 return if noop
377
378 fu_each_src_dest( src, dest ) do |s,d|
379 if FileTest.directory? s then
380 fu_copy_dir s, d, '.', preserve
381 else
382 fu_p_copy s, d, preserve
383 end
384 end
385 end
386
387 def fu_copy_dir( src, dest, rel, preserve )
388 fu_preserve_attr( preserve, "#{src}/#{rel}",
389 "#{dest}/#{rel}" ) {|s,d|
390 dir = File.expand_path(d) # to remove '/./'
391 Dir.mkdir dir unless FileTest.directory? dir
392 }
393 Dir.entries( "#{src}/#{rel}" ).each do |fn|
394 if FileTest.directory? File.join(src,rel,fn) then
395 next if /\A\.\.?\z/ === fn
396 fu_copy_dir src, dest, "#{rel}/#{fn}", preserve
397 else
398 fu_p_copy File.join(src,rel,fn), File.join(dest,rel,fn), preserve
399 end
400 end
401 end
402 private :fu_copy_dir
403
404 def fu_p_copy( src, dest, really )
405 fu_preserve_attr( really, src, dest ) {
406 copy_file src, dest
407 }
408 end
409 private :fu_p_copy
410
411 def fu_preserve_attr( really, src, dest )
412 unless really then
413 yield src, dest
414 return
415 end
416
417 st = File.stat(src)
418 yield src, dest
419 File.utime st.atime, st.mtime, dest
420 begin
421 File.chown st.uid, st.gid
422 rescue Errno::EPERM
423 File.chmod st.mode & 01777, dest # clear setuid/setgid
424 else
425 File.chmod st.mode, dest
426 end
427 end
428 private :fu_preserve_attr
429
430 def copy_file( src, dest )
431 st = r = w = nil
432
433 File.open( src, 'rb' ) {|r|
434 File.open( dest, 'wb' ) {|w|
435 st = r.stat
436 begin
437 while true do
438 w.write r.sysread(st.blksize)
439 end
440 rescue EOFError
441 end
442 } }
443 end
444
445
446 def mv( src, dest, *options )
447 noop, verbose, = fu_parseargs(options, :noop, :verbose)
448 fu_output_message "mv #{[src,dest].flatten.join ' '}" if verbose
449 return if noop
450
451 fu_each_src_dest( src, dest ) do |s,d|
452 if /djgpp|cygwin|mswin32/ === RUBY_PLATFORM and
453 FileTest.file? d then
454 File.unlink d
455 end
456
457 begin
458 File.rename s, d
459 rescue
460 if FileTest.symlink? s then
461 File.symlink File.readlink(s), dest
462 File.unlink s
463 else
464 st = File.stat(s)
465 copy_file s, d
466 File.unlink s
467 File.utime st.atime, st.mtime, d
468 begin
469 File.chown st.uid, st.gid, d
470 rescue
471 # ignore
472 end
473 end
474 end
475 end
476 end
477
478 alias move mv
479
480
481 def rm( list, *options )
482 force, noop, verbose, = fu_parseargs(options, :force, :noop, :verbose)
483 list = fu_list(list)
484 fu_output_message "rm#{force ? ' -f' : ''} #{list.join ' '}" if verbose
485 return if noop
486
487 list.each do |fname|
488 remove_file fname, force
489 end
490 end
491
492 alias remove rm
493
494 def rm_f( list, *options )
495 noop, verbose, = fu_parseargs(options, :noop, :verbose)
496 rm list, :force, *options
497 end
498
499 alias safe_unlink rm_f
500
501 def rm_r( list, *options )
502 force, noop, verbose, = fu_parseargs(options, :force, :noop, :verbose)
503 list = fu_list(list)
504 fu_output_message "rm -r#{force ? 'f' : ''} #{list.join ' '}" if verbose
505 return if noop
506
507 list.each do |fname|
508 begin
509 st = File.lstat(fname)
510 rescue
511 next if force
512 end
513 if st.symlink? then remove_file fname, force
514 elsif st.directory? then remove_dir fname, force
515 else remove_file fname, force
516 end
517 end
518 end
519
520 def rm_rf( list, *options )
521 noop, verbose, = fu_parseargs(options, :noop, :verbose)
522 rm_r list, :force, *options
523 end
524
525 def remove_file( fn, force = false )
526 first = true
527 begin
528 File.unlink fn
529 rescue Errno::ENOENT
530 force or raise
531 rescue
532 # rescue dos?
533 begin
534 if first then
535 first = false
536 File.chmod 0777, fn
537 retry
538 end
539 rescue
540 end
541 end
542 end
543
544 def remove_dir( dir, force = false )
545 Dir.foreach( dir ) do |file|
546 next if /\A\.\.?\z/ === file
547 path = "#{dir}/#{file}"
548 if FileTest.directory? path then remove_dir path, force
549 else remove_file path, force
550 end
551 end
552 begin
553 Dir.rmdir dir
554 rescue Errno::ENOENT
555 force or raise
556 end
557 end
558
559
560 def cmp( filea, fileb, *options )
561 verbose, = fu_parseargs(options, :verbose)
562 fu_output_message "cmp #{filea} #{fileb}" if verbose
563
564 sa = sb = nil
565 st = File.stat(filea)
566 File.size(fileb) == st.size or return true
567
568 File.open( filea, 'rb' ) {|a|
569 File.open( fileb, 'rb' ) {|b|
570 begin
571 while sa == sb do
572 sa = a.read( st.blksize )
573 sb = b.read( st.blksize )
574 unless sa and sb then
575 if sa.nil? and sb.nil? then
576 return true
577 end
578 end
579 end
580 rescue EOFError
581 ;
582 end
583 } }
584
585 false
586 end
587
588 alias identical? cmp
589
590 def install( src, dest, mode, *options )
591 noop, verbose, = fu_parseargs(options, :noop, :verbose)
592 fu_output_message "install #{[src,dest].flatten.join ' '}#{mode ? ' %o'%mode : ''}" if verbose
593 return if noop
594
595 fu_each_src_dest( src, dest ) do |s,d|
596 unless FileTest.exist? d and cmp(s,d) then
597 remove_file d, true
598 copy_file s, d
599 File.chmod mode, d if mode
600 end
601 end
602 end
603
604
605 def chmod( mode, list, *options )
606 noop, verbose, = fu_parseargs(options, :noop, :verbose)
607 list = fu_list(list)
608 fu_output_message sprintf('chmod %o %s', mode, list.join(' ')) if verbose
609 return if noop
610 File.chmod mode, *list
611 end
612
613 def touch( list, *options )
614 noop, verbose, = fu_parseargs(options, :noop, :verbose)
615 list = fu_list(list)
616 fu_output_message "touch #{list.join ' '}" if verbose
617 return if noop
618
619 t = Time.now
620 list.each do |fname|
621 begin
622 File.utime(t, t, fname)
623 rescue Errno::ENOENT
624 File.open(fname, 'a') { }
625 end
626 end
627 end
628
629
630 private
631
632 def fu_parseargs( opts, *flagdecl )
633 tab = {}
634 if opts.last == true or opts.last == false then
635 tab[:verbose] = opts.pop
636 end
637 while Symbol === opts.last do
638 tab[opts.pop] = true
639 end
640
641 flags = flagdecl.collect {|s| tab.delete(s) }
642 tab.empty? or raise ArgumentError, "wrong option :#{tab.keys.join(' :')}"
643
644 flags
645 end
646
647
648 def fu_list( arg )
649 Array === arg ? arg : [arg]
650 end
651
652 def fu_each_src_dest( src, dest )
653 unless Array === src then
654 yield src, fu_dest_filename(src, dest)
655 else
656 dir = dest
657 # FileTest.directory? dir or raise ArgumentError, "must be dir: #{dir}"
658 dir += (dir[-1,1] == '/') ? '' : '/'
659 src.each do |fn|
660 yield fn, dir + File.basename(fn)
661 end
662 end
663 end
664
665 def fu_dest_filename( src, dest )
666 if FileTest.directory? dest then
667 (dest[-1,1] == '/' ? dest : dest + '/') + File.basename(src)
668 else
669 dest
670 end
671 end
672
673
674 @fileutils_output = $stderr
675 @fileutils_label = 'fileutils.'
676
677 def fu_output_message( msg )
678 @fileutils_output ||= $stderr
679 @fileutils_label ||= 'fileutils.'
680 @fileutils_output.puts @fileutils_label + msg
681 end
682
683
684 extend self
685
686
687 OPT_TABLE = {
688 'cd' => %w( noop verbose ),
689 'chdir' => %w( noop verbose ),
690 'chmod' => %w( noop verbose ),
691 'cmp' => %w( verbose ),
692 'copy' => %w( preserve noop verbose ),
693 'cp' => %w( preserve noop verbose ),
694 'cp_r' => %w( preserve noop verbose ),
695 'identical?' => %w( verbose ),
696 'install' => %w( noop verbose ),
697 'link' => %w( force noop verbose ),
698 'ln' => %w( force noop verbose ),
699 'ln_s' => %w( force noop verbose ),
700 'ln_sf' => %w( noop verbose ),
701 'makedirs' => %w( noop verbose ),
702 'mkdir' => %w( noop verbose ),
703 'mkdir_p' => %w( noop verbose ),
704 'mkpath' => %w( noop verbose ),
705 'move' => %w( noop verbose ),
706 'mv' => %w( noop verbose ),
707 'remove' => %w( force noop verbose ),
708 'rm' => %w( force noop verbose ),
709 'rm_f' => %w( noop verbose ),
710 'rm_r' => %w( force noop verbose ),
711 'rm_rf' => %w( noop verbose ),
712 'rmdir' => %w( noop verbose ),
713 'safe_unlink' => %w( noop verbose ),
714 'symlink' => %w( force noop verbose ),
715 'touch' => %w( noop verbose ),
716 'uptodate?' => %w( verbose )
717 }
718
719
720 module Verbose
721
722 include FileUtils
723
724 @fileutils_output = $stderr
725 @fileutils_label = 'fileutils.'
726 @fileutils_verbose = true
727
728 FileUtils::OPT_TABLE.each do |name, opts|
729 next unless opts.include? 'verbose'
730 module_eval <<-End, __FILE__, __LINE__ + 1
731 def #{name}( *args )
732 unless defined? @fileutils_verbose then
733 @fileutils_verbose = true
734 end
735 args.push :verbose if @fileutils_verbose
736 super( *args )
737 end
738 End
739 end
740
741 extend self
742
743 end
744
745
746 module NoWrite
747
748 include FileUtils
749
750 @fileutils_output = $stderr
751 @fileutils_label = 'fileutils.'
752 @fileutils_nowrite = true
753
754 FileUtils::OPT_TABLE.each do |name, opts|
755 next unless opts.include? 'noop'
756 module_eval <<-End, __FILE__, __LINE__ + 1
757 def #{name}( *args )
758 unless defined? @fileutils_nowrite then
759 @fileutils_nowrite = true
760 end
761 args.push :noop if @fileutils_nowrite
762 super( *args )
763 end
764 End
765 end
766
767 extend self
768
769 end
770
771
772 class Operator
773
774 include FileUtils
775
776 def initialize( v = false )
777 @verbose = v
778 @noop = false
779 @force = false
780 @preserve = false
781 end
782
783 attr_accessor :verbose
784 attr_accessor :noop
785 attr_accessor :force
786 attr_accessor :preserve
787
788 FileUtils::OPT_TABLE.each do |name, opts|
789 s = opts.collect {|i| "args.unshift :#{i} if @#{i}" }.join(' '*10+"\n")
790 module_eval <<-End, __FILE__, __LINE__ + 1
791 def #{name}( *args )
792 #{s}
793 super( *args )
794 end
795 End
796 end
797
798 end
799
800 end