Directory Synchronizer
oocp.rbは、2つのディレクトリを同期(内容を等しく)させ
るためのコピーコマンドです。
またこのパッケージには好きなポリシーでコピーコマンドを作るため
のライブラリdir-compare.rbが同梱されています。
dir-compare.rb を ruby が require 可能なディレクトリ
に置いてください。また、起動コマンドは oocp.rb です。
2 つのディレクトリ source-dir と target-dir を同期させたい
場合は、
ruby oocp.rb source-dir target-dir
としてください。このとき source-dir のファイルと
ディレクトリが target-dir に次の条件の下に
コピーされます。
source-dir と target-dir の両方に同名のファイルがあった場合、source-dir 側の方が新しい場合コピーする。古い場合はコピーしない(この場合は同期させない)。
source-dir にないファイルやディレクトリが target-dir にある場合、そのファイルあるいはディレクトリは削除される。
source-dir にあるファイルやディレクトリが target-dir にない場合、そのファイルあるいはディレクトリは無条件にコピーされる。
.* または *~ にマッチするファイルはコピーしない。
なお、ファイルやディレクトリの「削除」とは target-dir と
同位のディレクトリに作る trash というディレクトリへの移動です。
(例: target-dir が abc/efg なら abc/trash 。)
このディレクトリはコピーコマンドへの第3引数として次のように指定する
こともできます。
ruby oocp.rb source-dir target-dir trash-dir
また、-s オプションを使って、
ruby oocp.rb -s source-dir target-dir
とすると、実際にコピーは行わず、実行されるはずの手続きを表示します。
oocp.rb は複数のディレクトリを比較するライブラリ
dir-compare.rb のサンプルアプリケーションです。
ここでは以下の dir-compare.rb の使用例があげられています。
また以下がこのライブラリで定義されているクラスです。
ファイルのコピーコマンド: cp.rb
次のコマンドはファイルとディレクトリの再帰的なコピーを行います。
#!/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)
コピーコマンドを作るにはまず、 DirPairWalker
というクラスを継承して新しいクラス (例えば Cp) を
作り、そのインスタンスを DirCompare.new の引数にして
あるオブジェクト(例えば dc) を作ります。
そして、操作の対象になるディレクトリは、その dc.run
の引数として与えます。
この run メソッドで実行される処理は Cp の
決められた名前のメソッドとして記述します。ここでの
file_file(s, t) と file_non(s, t)
と dir_non(s, t)
という名前のメソッドは、コピー元のファイル名が s
コピー先のファイル名が t として起動され、
それぞれ、s も t も存在する場合と、t のみ存在しない
場合と、s がディレクトリで t が存在しない場合に
起動されます。
このペア (s, t) は
(source, taget) のサブディレクトリ内の全てのファイル
を再帰的に走ります。
詳細は DirPairWalkerのリファレンスを見てください。
ディレクトリの同期コマンド: dir-sync.rb
次のコマンドは、2つのディレクトリを比較し、 両方を最新の状態に更新します。
#!/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)
file_lt_file(s, t)、file_gt_file(s, t) というメソッドは、s より t が、 新しい場合、古い場合にそれぞれ起動されます。
ディレクトリの共通部分の表示: dir-common.rb
次のコマンドは、2つのディレクトリを比較し、 共通のファイルを表示します。
#!/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)
ここでは、一方のディレクトリにのみサブディレクトリがある場合、
その中への探索を
throw :prune で中断することにより、無駄な処理を
させないようにしています。
ディレクトリの削除: rm-r.rb
次のコマンドは、ディレクトリを削除します。
#!/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 は、ディレクトリに入るときでは
なく、出るときに起動されます。DirWalker を参照してください。
ディレクトリの上書き移動: mv-o.rb
このコマンドは、ディレクトリを移動させます。すなわち、
"mv-o.rb source target"
は
"rm -r target; mv source target"
とほぼ同等ですが、
target
にのみあるファイルと
ディレクトリはそのまま残されます。
また、もし source と target がディレクトリなら、このコマンドは
"cp -r source/* target; rm -r source"
ともほぼ同等ですが、可能な限り rename (File.mv) を用います。
#!/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)
*_out という名前のメソッドは、ディレクトリに入るときでは
なく、出るときに起動されます。DirPairWalker を参照してください。
このクラスは DirCompare オブジェクト生成時に渡される
ビジターのクラスです。ビジターとは、目的のファイルやディレクトリに
出会うごとにすべき処理が定義されたオブジェクトです。
実際に起動されるのは DirCompare オブジェ
クトのメソッド run が起動された時です。
file(s)s がファイルの時起動される
dir(s)s がディレクトリの時起動される
dir_out(s)s がディレクトリで、そこから出て行くとき起動される。
run(mod, sqf)DirCompare クラスの run が内部で直接起動するメソッドです。 このメソッドが上記 3 つのメソッドを再度呼び出しています。 この sqf は QuasiFile オブジェクトです。mod はディレクトリに入るとき :in、出るとき :out、 ファイルのとき :empty です。
このクラスは file(s)、dir(s)、dir_out(s) の s
にファイル名(String)ではなく、それをラップした
QuasiFile が渡るだけで、あとは DirPairWalker と
同じです。
このクラスは DirCompare オブジェクト生成時に渡される
ビジターのクラスです。ビジターとは、目的のファイルやディレクトリに
出会うごとにすべき処理が定義されたオブジェクトです。
実際に起動されるのは DirCompare オブジェ
クトのメソッド run が起動された時です。
起動の順番はサブディレクトリの「深さ優先」で行われます。
s と t は、ソースとターゲットになるファイルあるいは ディレクトリを表します。
dir_dir(s, t)s と t がディレクトリであるときに起動される
dir_dir_out(s, t)s と t がディレクトリで、そこから 出て行くとき起動される
dir_file(s, t)s がディレクトリ、t がファイルであるときに起動される
dir_file_out(s, t)s がディレクトリ、t がファイルで、その ディレクトリから出るとき起動される
dir_non(s, t)s がディレクトリ、t が存在しないときに起動される
dir_non_out(s, t)s がディレクトリで t は存在せず、そのディレクトリから 出るとき起動される
file_dir(s, t)s がファイル、t がディレクトリのときに起動される
file_dir_out(s, t)s がファイル、t がディレクトリで、そのディレクトリ から出るとき起動される
file_file(s, t)s と t がファイルでのとき file_eq_file、 file_gt_file、 file_lt_file に起動される。
file_eq_file(s, t)s と t がファイルで、更新時刻が等しいときに起動され、 更に file_file を起動する
file_gt_file(s, t)s と t がファイルで、s の方が新しいとき起動され、 更に file_file を起動する
file_lt_file(s, t)s と t がファイルで、t の方が新しいとき起動され、 更に file_file を起動する
file_non(s, t)s がファイルで、t が存在しないとき起動される
non_dir(s, t)s が存在せず、t がディレクトリのとき起動される
non_dir_out(s, t)s が存在せず、t がディレクトリで、そのディレクトリから 出るとき起動される
non_file(s, t)s が存在せず、t がファイルのとき起動される
non_non(s, t)s も t も存在しないとき起動される。
run(mod, sqf, tqf)
DirCompare クラスの run が内部で直接起動するメソッドです。
このメソッドが上記 17 メソッドを再度呼び出しています。
この sqf と tqf は QuasiFile オブジェクトです。
mod については DirCompare#parse を見てください。
このクラスは file_gt_file(s, t) の s, t
にファイル名(String)ではなく、それをラップした
QuasiFile が渡るだけで、あとは DirPairWalker と
同じです。
このクラスは複数のディレクトリを比較するためのクラスです。
DirCompare.new(visitor)visitor をビジターとしてインスタンスを生成します。
run([path1, [path2, [path3,..]]])
compare([path1, [path2, [path3,..]]])
ビジターのメソッド run(mod, qf1, qf2, qf3,..) を起動します。
ここで、qf1, qf2, qf3,.. は path1, path2, path3,.. から作られた
QuasiFile オブジェクトです。
mod については parse を見てください。
結局、このメソッドはこの次のように作られています。
def run(*paths)
qfs = paths.map{|path| QuasiFile.new(path)}
parse(*qfs) do |mod, *qfs0|
@visitor.run(mod, *qfs0)
end
end
ここでインスタンス変数 @visitor は、自身が生成された とき添えられたビジターを指しています。
parse([qf1, [qf2, [qf3,..]]])ディレクトリ QuasiFile オブジェクト qf1, qf2, qf3, ... を引数にして起動し、ブロックパラメータに、mod と QuasiFile オブジェクトとして表された それぞれの内容(content)を代入しながら、イテレートします。 イテレートの順番は「深さ優先」です。
mod は次のように決まります。
:in ... ブロックパラメータのどれかがディレクトリであり、そこに入る
:out ... そこから出る
:empty ... ブロックパラメータのどれもディレクトリでない(すなわちファイルか存在しないパスである)
複数のディレクトリのファイルを同期させる例
#!/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
ここでは、存在しないファイルは比較によって最も小さいとされる <=>の性質を利用しています。
throw :prune をすると、より深いサブディレクトリへの
探索を中断できます。
これはディレクトリあるいはファイルの情報を保持するオブジェクト のためのクラスです。具体的には String と File::Stat のラッパーで、更に exist? と content を持つクラスです。
QuasiFile.new(path)ディレクトリあるいはファイル path の情報を保持するインスタンスを 生成する。
このクラスは、 Stringの 以下のメソッド:
%, *, +, <<, [], []=, 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, ~
と、File::Stat の以下のメソッド:
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?
を備えています。ただし、存在しないファイル
に対しての File::Stat 系のメソッドの値は nil です。
それ以外のメソッドは以下の通りです。
path自分の名前を返します。
stat
自分の File::Stat オブジェクトを返します。
<=>(o)o も QuasiFile オブジェクトとして、ファイルの更新時刻を比較する。 ただし、存在しないファイルはもっとも古いと解釈する。
content自分のディレクトリとしての内容の配列。自分がファイルであるか存在しないパスなら空配列。
exist?実際に存在するファイルあるいはディレクトリであるとき真を返す。
2.00.00
1.00.00