二つのディレクトリの差分を別ディレクトリにコピーするスクリプトを作ってみました

ネットで調べ物をしていると、次のページを見つけました。Rubyの配列を上手く使っていて、非常に勉強になる内容でした。

>Ruby標準パッケージでディレクトリ比較を作ってrsyncモドキをする - それマグで！
>http://takuya-1st.hatenablog.jp/entry/20100921/1285054620

せっかくなので、私も似たようなRubyスクリプトを作成してみました。

二つのディレクトリを比較し、追加や変更のあったファイル/ディレクトリを、別のディレクトリにコピーします。あるファイル/ディレクトリ一式を、他の人に提供する際に、2回目以降は、差分のファイル/ディレクトリのみを提供したい場合などに、使えるスクリプトです。（とは書いて見たものの、利用シーンは非常に限られていますね。）

# 使い方
次のように引数を3つ渡して、実行します。

```bat:diffcopy.bat
ruby diffcopy.rb 変更前ディレクトリ 変更後ディレクトリ 出力ディレクトリ
```

たとえば、次のようなディレクトリに対して、スクリプト実行するとします。

変更前ディレクトリの中身
>DiffFile.txt
>Dir1OnlyFile.txt
>SameFile.txt

変更後ディレクトリの中身
>DiffFile.txt　※「変更前ディレクトリ」の同名ファイルと差異あり
>Dir2OnlyFile.txt
>SameFile.txt　※「変更前ディレクトリ」の同名ファイルと同じ

変更後ディレクトリにて追加、変更のあったファイルのみが選択されて、次のようなディレクトリが作成されます。この例では、ファイルのみを扱っていますが、サブディレクトリも同様に処理されます。

出力ディレクトリ
>DiffFile.txt　※「変更後ディレクトリ」のファイル
>Dir2OnlyFile.txt

# スクリプト
```ruby:diffcopy.rb
require 'fileutils'
require 'pathname'

# ------------------------------------------------------------
# DiffCopyコマンドクラス
# ------------------------------------------------------------
class DiffCopy
	def initialize(argv)
		@args = Arguments.new(argv)
	end
	
	def run
		@args.parse
		if !@args.valid?
			@args.print_usage
			return
		end
		
		patharr1 = PathArray.new(@args.indir1)
		patharr2 = PathArray.new(@args.indir2)
		
		diffdata = DiffData.new(patharr1, patharr2)
		
		filecopy = FileCopy.new(diffdata.update_path_array, @args.outdir)
		filecopy.run
	end
end

# ------------------------------------------------------------
# コマンドライン引数クラス
# ------------------------------------------------------------
class Arguments
	attr_reader :indir1, :indir2, :outdir
	
	def initialize(argv)
		@argv = argv
	end
	
	def parse
		@indir1 = @argv[0]
		@indir2 = @argv[1]
		@outdir = @argv[2]
	end
	
	def valid?
		@indir1 && @indir2 && @outdir
	end
	
	def print_usage
		puts "Usage: diffcopy 入力ディレクトリ1 入力ディレクトリ2 出力ディレクトリ"
	end
end

# ------------------------------------------------------------
# パス配列クラス
# ------------------------------------------------------------
class PathArray < Array
	attr_reader :dir
	
	def initialize(dir, array = nil)
		@dir = dir
		if array == nil
			# 与えられたディレクトリ配下の、ファイル/ディレクトリを再帰的に取得する。
			Dir.chdir(dir) {
				concat(Dir.glob("**/*"))
			}
		else
			concat(array)
		end
	end
	
	# 追加ファイル/フォルダを取得する。
	def add_path_array(other)
		PathArray.new(dir, self - other)
	end
	
	# 削除ファイル/フォルダを取得する。
	def del_path_array(other)
		PathArray.new(other.dir, other - self)
	end
	
	# 変更ファイルを取得する。
	# ※フォルダの中身は別途確認するため、フォルダは、追加/削除のみ確認する。
	def mod_path_array(other)
		PathArray.new(dir, (self & other).delete_if {|e|
			pathname = Pathname.new(File.expand_path(e, self.dir))
			if pathname.directory?
				true
			elsif pathname.file?
				FileUtils.cmp(File.expand_path(e, self.dir), File.expand_path(e, other.dir))
			else
				$stderr.puts("対象外パス：#{src}")
			end
		})
	end
end

# ------------------------------------------------------------
# 差分クラス
# ------------------------------------------------------------
class DiffData
	attr_reader :add_path_array, :del_path_array, :mod_path_array
	
	def initialize(path_array1, path_array2)
		@add_path_array = path_array2.add_path_array(path_array1)
		@del_path_array = path_array2.del_path_array(path_array1)
		@mod_path_array = path_array2.mod_path_array(path_array1)
	end
	
	def update_path_array
		PathArray.new(@add_path_array.dir, (@add_path_array + @mod_path_array).sort)
	end
end

# ------------------------------------------------------------
# ファイル/ディレクトリ差分コピークラス
# ------------------------------------------------------------
class FileCopy
	def initialize(path_array, out_dir)
		@path_array = path_array
		@out_dir = out_dir
	end
	
	def run
		@path_array.each {|e|
			src = Pathname.new(File.expand_path(e, @path_array.dir))
			dest = Pathname.new(File.expand_path(e, @out_dir))
			if src.directory?
				FileUtils.mkdir_p(dest, {:verbose => $VERBOSE})
			elsif src.file?
				FileUtils.mkdir_p(dest.parent, {:verbose => $VERBOSE})
				FileUtils.cp(src, dest, {:verbose => $VERBOSE})
			else
				$stderr.puts("対象外パス：#{src}")
			end
		}
	end
end

# ------------------------------------------------------------
# コマンド実行
# ------------------------------------------------------------
if __FILE__ == $0
	main = DiffCopy.new(ARGV)
	main.run
end
```

# リポジトリ
上記のスクリプトは、次の場所で管理してあります。
https://github.com/kurukurupapa/TryRuby.git のdiffcopy.rb

# 動作確認環境
- Windows 8.1 64bit
- ruby 1.9.2p0 (2010-08-18 revision 29036) [x64-mswin64_80]

# 参考ページ
- [Ruby標準パッケージでディレクトリ比較を作ってrsyncモドキをする - それマグで！](http://takuya-1st.hatenablog.jp/entry/20100921/1285054620)