ファイル検索用のインタプリタを作成する過程から、 interpreter パターンを学ぶ ** すべてのファイルを検索する/名前でファイルを検索する/大きなファイル/書き込み可能ファイル require 'find' class Expression end # すべてのファイルを検索する class All < Expression def evaluate(dir) results= [] Find.find(dir) do |p| next unless File.file?(p) results << p end results end end expr_all = All.new files = expr_all.evaluate('test_dir') files # => ["test_dir/small2.txt", "test_dir/subdir/small.jpg", "test_dir/subdir/other.mp3", "test_dir/small1.txt", "test_dir/big.mp3", "test_dir/small.mp3", "test_dir/big2.mp3"] # 名前でファイルを検索する class FileName < Expression def initialize(pattern) @pattern = pattern end def evaluate(dir) results= [] Find.find(dir) do |p| next unless File.file?(p) name = File.basename(p) results << p if File.fnmatch(@pattern, name) end results end end expr_mp3 = FileName.new('*.mp3') mp3s = expr_mp3.evaluate('test_dir') mp3s # => ["test_dir/subdir/other.mp3", "test_dir/big.mp3", "test_dir/small.mp3", "test_dir/big2.mp3"] # 大きなファイル class Bigger < Expression def initialize(size) @size = size end def evaluate(dir) results = [] Find.find(dir) do |p| next unless File.file?(p) results << p if( File.size(p) > @size) end results end end expr_big = Bigger.new(80) bigs = expr_big.evaluate('test_dir') bigs # => ["test_dir/subdir/other.mp3", "test_dir/big.mp3", "test_dir/big2.mp3"] #書き込み可能ファイル class Writable < Expression def evaluate(dir) results = [] Find.find(dir) do |p| next unless File.file?(p) results << p if( File.writable?(p) ) end results end end expr_writable = Writable.new writables = expr_writable.evaluate('test_dir') writables # => ["test_dir/small2.txt", "test_dir/subdir/small.jpg", "test_dir/subdir/other.mp3", "test_dir/small1.txt", "test_dir/big.mp3", "test_dir/small.mp3", "test_dir/big2.mp3"] ** Not,And,Or を使ったより複雑な検索 class Not < Expression def initialize(expression) @expression = expression end def evaluate(dir) All.new.evaluate(dir) - @expression.evaluate(dir) end end expr_not_writable = Not.new(Writable.new) readonly_files = expr_not_writable.evaluate('test_dir') readonly_files # => ["test_dir/small1.txt"] small_expr = Not.new(Bigger.new(1024)) small_files = small_expr.evaluate('test_dir') small_files # => ["test_dir/small2.txt", "test_dir/subdir/small.jpg", "test_dir/small1.txt", "test_dir/small.mp3"] not_mp3_expr = Not.new(FileName.new('*.mp3')) not_mp3_files = not_mp3_expr.evaluate('test_dir') not_mp3_files # => ["test_dir/small2.txt", "test_dir/subdir/small.jpg", "test_dir/small1.txt"] class Or < Expression def initialize(expression1, expression2) @expression1 = expression1 @expression2 = expression2 end def evaluate(dir) result1 = @expression1.evaluate(dir) result2 = @expression2.evaluate(dir) (result1 + result2).sort.uniq end end # MP3ファイルまたは1KBより大きいファイルを一度に検索する big_or_mp3_expr = Or.new(Bigger.new(1024), FileName.new('*.mp3')) big_or_mp3s = big_or_mp3_expr.evaluate('test_dir') big_or_mp3s # => ["test_dir/big.mp3", "test_dir/big2.mp3", "test_dir/small.mp3", "test_dir/subdir/other.mp3"] #このような複雑なASTを作ると、別のコンテキストでそれを繰り返し利用できる。 # big_or_mp3_expr.evaluate('test_dir') # big_or_mp3_expr.evaluate('/home/me/tmp') class And < Expression def initialize(expression1, expression2) @expression1 = expression1 @expression2 = expression2 end def evaluate(dir) result1 = @expression1.evaluate(dir) result2 = @expression2.evaluate(dir) (result1 & result2) end end ** パーサを使わないインタープリタにすることにした/便利なショートカットを導入(DSL) 上位クラス(Expression) にAnd,Or のシンタックスシュガーを作成して書きやすくする class Expression def |(other) Or.new(self, other) end def &(other) And.new(self, other) end end expr = Or.new( And.new(Bigger.new(1024), Not.new(Writable.new )), FileName.new('*.mp3')) files = expr.evaluate('test_dir') files # => ["test_dir/big.mp3", "test_dir/big2.mp3", "test_dir/small.mp3", "test_dir/subdir/other.mp3"] expr = (Bigger.new(1024) & Not.new(Writable.new)) | FileName.new('*.mp3') #これは短かくて読みやすい! files = expr.evaluate('test_dir') files # => ["test_dir/big.mp3", "test_dir/big2.mp3", "test_dir/small.mp3", "test_dir/subdir/other.mp3"]