Directory Synchronizer
This is a copy command to synchronize two directories.
The package includes the library dir-compare.rb
,
which enables us to make arbitrary copy commands.
Put dir-compare.rb
in the directory where Ruby can load.
The copy command is oocp.rb
.
To synchronize the directories source-dir
and target-dir
,
do as follows:
ruby oocp.rb source-dir target-dir
Then the files in source-dir
are copied in target-dir
under the condition:
source-dir
and in target-dir
, the one in source-dir
is copied if it is newer than that in target-dir
, or it is not copied (so they aren't synchronized in this case).
target-dir
and not in source-dir
is deleted.
source-dir
and not in target-dir
is copied.
.*
or *~
is not copied.
"delete" means moving to trash
directory.
This is where target-dir
is. (For example,
if target-dir
is abc/efg
then the trash directory
is abc/trash
.)
The trash directory can be set as the third parameter as follows:
ruby oocp.rb source-dir target-dir trash-dir
If the option -s
is used, no files are copied but
the process which will be done is listed.
ruby oocp.rb -s source-dir target-dir
oocp.rb
is a sample application script using
this library dir-compare.rb
. Here we show
other sample scripts for using dir-compare.rb
:
The followings are the classes defined in this library.
Copy Command: cp.rb
The next command copys files and directories recursively.
#!/usr/local/bin/ruby require "dir-compare" require "ftools" class Cp < DirPairWalker def file_file(s, t) File.cp(s, t) end def file_non(s, t) File.cp(s, t) end def dir_non(s, t) File.mkpath(t) end end source, target = ARGV visitor = Cp.new dc = DirCompare.new(visitor) dc.run(source, target)
To make a copy command, create the new class
(Cp
for example) which inherits
DirPairWalker. And create the object (dc
)
by DirCompare.new
with the parameter of the instance
of Cp
. Then the comparing is done when we invoke
dc.run
with the directories as the parameters.
The actual commands are
defined as the certain methods of Cp
.
The methods file_file(s, t), file_non(s, t)
and dir_non(s, t)
are called with the source file name s and the target
file name t. This pair of (s, t) runs recursively
over all files and subdirectories of (source, taget).
If s and t exist then file_file(s, t) is called, and
if s exists and t doesn't exist then
file_non(s, t) is called.
If s is a direcotry and t doesn't exist,
dir_non(s, t) is called.
See the reference of DirPairWalker for the detail.
Synchronizer for two directories: dir-sync.rb
The next command compares two directories and updates all files in them.
#!/usr/local/bin/ruby require "dir-compare" require "ftools" class DirSync < DirPairWalker def file_lt_file(s, t) File.cp(t, s) end def file_gt_file(s, t) File.cp(s, t) end def file_non(s, t) File.cp(s, t) end def non_file(s, t) File.cp(t, s) end def dir_non(s, t) File.mkpath(t) end def non_dir(s, t) File.mkpath(s) end end source, target = ARGV visitor = DirSync.new dc = DirCompare.new(visitor) dc.run(source, target)
The methods file_lt_file(s, t) and file_gt_file(s, t) are invoked when s is older than t or newer than it respectively.
Display the intersection of two directories: dir-common.rb
The next command compares two directories and show common files.
#!/usr/local/bin/ruby require "dir-compare" require "ftools" class DirCommon < DirPairWalker def file_file(s, t) puts "#{s} <-> #{t}" end def dir_file(s, t) throw :prune end def dir_non(s, t) throw :prune end def file_dir(s, t) throw :prune end def non_dir(s, t) throw :prune end end source, target = ARGV visitor = DirCommon.new dc = DirCompare.new(visitor) dc.run(source, target)
We use throw :prune
to avoid vain search.
Remove directories: rm-r.rb
The next command removes the directory recursively.
#!/usr/local/bin/ruby require "dir-compare" require "ftools" class Rm_r < DirWalker def file(s) File.unlink(s) end def dir_out(s) Dir.unlink(s) end end rm = Rm_r.new dc = DirCompare.new(rm) path = ARGV.shift dc.run(path)
dir_out
is invoked when the walker
leaves the directory. See DirWalker.
Over-write Move: mv-o.rb
This command moves the directory. That is, the command:
"mv-o.rb source target
"
is almost equivalent to
"rm -r target; mv source target
"
, but the files and directories only in
target
are kept without change.
If source
and target
are directories,
the command is also almost equivalent to
"cp -r source/* target; rm -r source
"
, but it uses rename
(File.mv
) as far as possible for performance.
#!/usr/local/bin/ruby require "dir-compare" require "ftools" class Mv_o < DirPairWalker def initialize @file_dir = false end def file_file(s, t) File.mv(s, t) end def file_dir(s, t) @file_dir = true end def file_dir_out(s, t) @file_dir = false Dir.unlink(t) File.mv(s, t) end def file_non(s, t) dir = File.dirname(t) File.mkpath(dir) unless File.directory?(dir) File.mv(s, t) end def non_file(s, t) if @file_dir File.unlink(t) end end def dir_file(s, t) File.unlink(t) File.mv(s, t) throw :prune end def dir_dir_out(s, t) Dir.rmdir(s) end def dir_non(s, t) dir = File.dirname(t) File.mkpath(dir) unless File.directory?(dir) File.mv(s, t) throw :prune end end visitor = Mv_o.new dc = DirCompare.new(visitor) source, target = ARGV dc.compare(source, target)
The methods named *_out
are invoked when the walker
leaves the directory. See DirPairWalker.
This is the visitor class of a directory, which is given to
DirCompare.new
.
"visitor" is the object that has the definitions of its behavior
when it meets the files or directories.
The following methods are invoked by DirCompare#run
.
file(s)
Called when s is a file.
dir(s)
Called when s is a directory.
dir_out(s)
Called when s is a directory and the walker leaves there.
run(mod, sqf)
This method is called directly by DirCompare#run
.
And this calls the above 3 methods.
sqf is the QuasiFile objects.
mod is :in when the walker goes into the directory,
is :out when it leaves and is :empty when it meets
the file.
This is the similar class to DirWalker.
The difference is that the parameters
s of the methods
are not String
but QuasiFile - object.
This is the visitor class of two directories, which is given to
DirCompare.new
.
"visitor" is the object that has the definitions of its behavior
when it meets the files or directories.
The following methods are invoked by DirCompare#run
.
The order of calling is "depth-first" according to the subdirectories.
s (resp. t) represents the source (resp. target) files or directory.
dir_dir(s, t)
Called when s and t are directories.
dir_dir_out(s, t)
Called when s and t are directories and the walker leaves there.
dir_file(s, t)
Called when s is a directory and t is a file.
dir_file_out(s, t)
Called when s is a directory, t is a file and the walker leaves there.
dir_non(s, t)
Called when s is a directory and t does not exist.
dir_non_out(s, t)
Called when s is a directory, t does not exist and the walker leaves there.
file_dir(s, t)
Called when s is a file and t is a directory.
file_dir_out(s, t)
Called when s is a file, t is a directory and the walker leaves there.
file_file(s, t)
Called by file_eq_file, file_gt_file and file_lt_file when s and t are files .
file_eq_file(s, t)
Called when s and t are files and s is as new as t, and calls file_file again.
file_gt_file(s, t)
Called when s and t are files and s is newer than t, and calls file_file again.
file_lt_file(s, t)
Called when s and t are files and s is older than t, and calls file_file again.
file_non(s, t)
Called when s is a file and t does not exist.
non_dir(s, t)
Called when s does not exist and t is a directory.
non_dir_out(s, t)
Called when s does not exist, t is a directory and the walker leaves there.
non_file(s, t)
Called when s does not exist and t is a file.
non_non(s, t)
Called when s and t do not exist.
run(mod, sqf, tqf)
This method is called directly by DirCompare#run
.
And this calls the above 17 methods.
sqf and tqf is the QuasiFile objects.
For mod, see DirCompare#parse.
This is the similar class to DirPairWalker.
The difference is that the parameters
(s and t) of the 17 methods
are not String
but QuasiFile - object.
This is the class to compare plural directories.
DirCompare.new(visitor)
Creates the instance with visitor as the visitor object.
run([path1, [path2, [path3,..]]])
compare([path1, [path2, [path3,..]]])
Invokes the visitor's method run(mod, qf1, qf2, qf3,..)
.
qf1, qf2, qf3,.. are the QuasiFile objects
created by path1, path2, path3,...
For mod, see parse.
This method is defined as follows:
def run(*paths) qfs = paths.map{|path| QuasiFile.new(path)} parse(*qfs) do |mod, *qfs0| @visitor.run(mod, *qfs0) end end
Here the instance variable @visitor indicates the parameter accompanied when self is created.
parse([qf1, [qf2, [qf3,..]]])
Called with parameters qf1, qf2, qf3, ... those are QuasiFile objects, and iterates with some block parameters. These block parameters are the mod and QuasiFile objects which run over all subdirectories and files of the contents of qf1, qf2, qf3, ... The order of iteration is "depth-first".
mod is determined by the next way:
:in ... at least one of the block parameters is a directory and the walker goes into the directory.
:out ... it leaves there.
:empty ... none of the block parameters is a directory (i.e. they are files or non-existence).
Example: Synchronize files in plural directories
#!/usr/local/bin/ruby require "dir-compare" require "ftools" qfa = ARGV.map{|dir| QuasiFile.new(dir)} dc = DirCompare.new dc.parse(*qfa) do |mod, *qfb| if mod == :empty m = (0...qfb.size).max{|i, j| qfb[i] <=> qfb[j]} qfb.each_with_index do |qf, i| File.cp(qfb[m].path, qf.path) if i != m end end end
We use the property of <=> that the non-existing file is the oldest here.
We can stop the further searching of subdirectories by
invoking throw :prune
.
This is the fake file class which has the information of files. In concrete terms, this is the wrapper class of String and File::Stat and has the methods exist? and content.
QuasiFile.new(path)
Creates instance having the information of the file or the directory represented by the String path.
This class has the methods in String listed below:
%, *, +, <<, [], []=, capitalize, capitalize!, center, chomp, chomp!, chop, chop!, collect, concat, count, crypt, delete, delete!, detect, downcase, downcase!, dump, each, each_byte, each_line, each_with_index, empty?, entries, find, find_all, grep, gsub, gsub!, hex, include?, index, intern, length, ljust, map, max, member?, min, next, next!, oct, reject, replace, reverse, reverse!, rindex, rjust, scan, select, slice, slice!, sort, split, squeeze, squeeze!, strip, strip!, sub, sub!, succ, succ!, sum, swapcase, swapcase!, to_f, to_i, to_str, tr, tr!, tr_s, tr_s!, unpack, upcase, upcase!, upto, ~
and the methods in File::Stat listed below:
atime, blksize, blockdev?, blocks, chardev?, ctime, dev, directory?, executable?, executable_real?, file?, ftype, gid, grpowned?, ino, mode, mtime, nlink, owned?, pipe?, rdev, readable?, readable_real?, setgid?, setuid?, size, size?, socket?, sticky?, symlink?, uid, writable?, writable_real?, zero?
The return value of the methods in File::Stat
for
the path which doesn't exist is nil
.
Other methods are following:
path
Returns own name.
stat
Returns own File::Stat
object.
<=>(o)
When o is QuasiFile object, returns the comparing of modifying time. The non-existing file is assumed to be the oldest.
content
Returns the content as an array when self is a directory. When self is a file or does not exist, returns the empty array.
exist?
Returns true when self is a file or a directory which exists.
2.00.00
1.00.00