内部DSLパターン (1)
『Rubyによるデザインパターン』(ラス・オルセン著 ピアソン・エデュケーション刊)の例をPythonに変換して書いています。目次
今回は、内部DSL(ドメイン特化言語)パターンです。
前回のInterpreterパターンで、特定のタスクのための簡単な言語を作りました。このような小言語を、DSL(ドメイン特化言語)と言います。
動的言語であるRubyは、幾つか関数・クラスを定義するだけで、ユーザーのDSLになるように仕立て上げる事が出来ます。
これを、内部DSLと言います。
もちろん、Pythonでも同じように内部DSLを作れます
・・・と、言いたい所ですが、チョット厳しいかも?
バックアッププログラムを作る事を考えます。
そのDSLは次のような文法です。バックアップするファイルの条件式は、前回作ったものを流用します。
#backup.pr #Rubyフォルダの、拡張子がtmpで無いファイルを全てバックアップ backup('d:/Owner/My Documents/Ruby', ~filename('*.tmp')) #My Musicフォルダの音楽ファイルをバックアップ backup('d:/Owner/My Documents/My Music', filename('*.mp3') | filename('*.ogg')) #Pythonフォルダの全ファイルをバックアップ backup('d:/Owner/My Documents/Python') #バックアップ先 to('F:/backups') #60分ごとにバックアップ interval(60)
さて、これをどうやって構文解析するかですが、
正規表現やPLYを使ってもよいのですが、よく見るとこのDSLは、Pythonの文法と同じです。
ならば、手っ取り早くPythonを流用してしまいましょう
from finder import * #DSL用の関数(仮) def backup(dirpath, find_expression=All()): print "backup called, source dir=%s, find expr=%s"%( dirpath, find_expression) def to(dirpath): print "to called, backup dir=%s"%(dirpath) def interval(minutes): print "interval called, minutes=%s"%(minutes,) defstr = open("backup.pr").read() # バックアップ設定ファイルの読み込み exec defstr #文字列をPythonスクリプトであるかのように実行
内部DSLの利点は、Pythonの機能をそのまま流用できる事。たとえば、パス名にシングルクオートやバックスラッシュが入っている場合、
to("F:\\doloop\'s backup")
等と、エスケープする必要がありますが、
エスケープを処理する正規表現を書くのは退屈な作業です。内部DSLなら、こういったものをタダで手に入れられるのです。
足りないところを補完したのが次のコードです。
from finder import * import os import time import shutil class Backup(object): def __init__(self): self.data_sources = [] self.backup_directory = "C:/backup" self.interval = 60 def append(self, data_source): self.data_sources.append(data_source) def set_backup_directory(self, directory): self.backup_directory = directory def set_interval(self, minutes): self.interval = minutes def backup_files(self): this_backup_dir = time.strftime("%Y-%m-%d %H-%M-%S") this_backup_path = os.path.join(self.backup_directory, this_backup_dir) for source in self.data_sources: source.backup(this_backup_path) def run(self): while True: self.backup_files() time.sleep(self.interval*60) Backup = Backup() #簡単シングルトン class DataSource(object): def __init__(self, directory, find_expression): self.directory = directory self.find_expression = find_expression def backup(self, backup_directory): files = self.find_expression.evaluate(self.directory) for f in files: self.backup_file(f, backup_directory) def backup_file(self, filepath, backup_directory): if not os.path.isdir(backup_directory): os.makedirs(backup_directory) shutil.copy(filepath, backup_directory) def backup(dirpath, find_expression=All()): Backup.append(DataSource(dirpath, find_expression)) def to(dirpath): Backup.set_backup_directory(dirpath) def interval(minutes): Backup.set_interval(minutes) defstr = open("backup.pr").read() # バックアップ設定ファイルの読み込み exec defstr #文字列をPythonスクリプトであるかのように実行 Backup.run()