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