「Rubyによるデザインパターン」の読書メモです。
「内部」DSL
命令文をDSLの中に取り込んで、それをRubyプログラムとして解釈するアイディア
backup.pr
backup '/home/russ/documents'
backup '/home/russ/music', file_name('*.mp3') & file_name('*.wav')
backup '/home/russ/images', except(file_name('*.tmp'))
to '/external_drive/backups'
interval 60
test.rb
require 'finder'
require 'find'
class Expression
def |(other)
Or.new(self, other)
end
def &(other)
And.new(self, other)
end
end
class All < Expression
def evaluate(dir)
results = []
Find.find(dir) do |p|
next unless File.file?(p)
results << p
end
results
end
end
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
class Not < Expression
def initialize(expression)
@expression = expression
end
def evaluate(dir)
All.new.evaluate(dir) - @expression.evaluate(dir)
end
end
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
def backup(dir, find_expression=All.new)
puts "Backup called, source dir=#{dir} find expr=#{find_expression}"
end
def to(backup_directory)
puts "To called, backup dir=#{backup_directory}"
end
def interval(minutes)
puts "Interval called, interval = #{minutes} minutes"
end
def except(expression)
Not.new(expression)
end
def file_name(pattern)
FileName.new(pattern)
end
eval(File.read('backup.pr'))
まとめ
内部DSLの背景にある考え方は、とても簡単です。Rubyの構文ルールに合うようにあなたがDSLを定義します。そして、DSLで書かれた、やるべきことが記述されているプログラムに必要な基盤もあなたが定義します。