ファイルコピーツールのオプション管理クラス

今日は、最近ちくちくと作っているツールの動作オプションを管理するクラスを作りました。
とはいっても、オプションを管理するほど多機能ではないんですけどね。(笑

とりあえず、ソースコードはこんな感じです。

# -*- coding: utf-8 -*-

import types
import  xml.sax
from xml.dom.minidom import Document
from xml.sax.handler import ContentHandler

PREF_KEY_SAVE_WINDW_LAYOUT = 'save_window_layout'
PREF_KEY_WINDOW_POS = 'window_pos'
PREF_KEY_WINDOW_SIZE = 'window_size'
PREF_KEY_DEFAULT_DIRECTORY = 'default_dir'
PREF_KEY_KEEP_FILE_DIRECTORY = 'keep_file_directory'
PREF_KEY_VIEW_TREE_EXPANDED = 'expanded_tree'
PREF_KEY_VIEW_DRAW_TREE_LINE = 'draw_tree_line'
PREF_KEY_VIEW_SHOW_TOOLBAR = 'show_toolbar'
PREF_KEY_LIST_FILTER_EXTENSIONS = 'filter_extentions'

PREF_KEY_TYPE = 't'
PREF_KEY_VALUE = 'v'


ELEMENT_NAME_ROOT = 'fcp_prefs'
ELEMENT_NAME_KEY = 'pref_key'

ELEMENT_ATTR_VALUE_TYPE = 'type'
ELEMENT_ATTR_VALUE = 'val'

PREF_VAL_TYPE_BOOLEAN = 'BOOLEAN'
PREF_VAL_TYPE_INT = 'INT'
PREF_VAL_TYPE_STRING = 'STRING'
PREF_VAL_TYPE_TUPLE = 'TUPLE'
PREF_VAL_TYPE_LIST = 'LIST'


#
#
class FCPPrefGenerator:
    '''
    プリファレンスデータをXMLに変換するクラス
    '''
    def __init__(self):
        '''
        初期化
        '''
        # XML DOMドキュメントを生成し、
        # rootとなるタグを子供として追加しておく。
        self.doc = Document()
        self.root = self.doc.createElement(ELEMENT_NAME_ROOT)
        self.doc.appendChild(self.root)

        # プリファレンスの設定項目を文字列に変換するための
        # 関数辞書。
        self.genFuncDict = {
            PREF_VAL_TYPE_BOOLEAN: lambda a: str(a),
            PREF_VAL_TYPE_TUPLE: lambda a: self.seq_to_str(a),
            PREF_VAL_TYPE_LIST: lambda a: self.seq_to_str(a),
            PREF_VAL_TYPE_STRING: lambda a: a,
            PREF_VAL_TYPE_INT: lambda a: str(a)}

    def __del__(self):
        '''
        破棄
        '''
        self.doc.unlink()

    def seq_to_str(self, seq):
        '''
        シーケンスの要素をカンマ区切りの文字列に変換する
        '''
        if seq == None or len(seq) == 0:
            return ''
        return reduce(lambda a, b: str(a)+','+str(b), seq)

    def generate(self, prefDict, encode='utf-8'):
        '''
        XMLを生成する
        '''
        for k, vdict in prefDict.iteritems():
            # プリファレンス設定項目ごとのタグを作成
            elm = self.doc.createElement(k)
            
            # 値のタイプを属性に追加
            valType = vdict[PREF_KEY_TYPE]
            elm.setAttribute(ELEMENT_ATTR_VALUE_TYPE, valType)            

            # 値を属性に追加
            val = vdict[PREF_KEY_VALUE]            
            elm.setAttribute(ELEMENT_ATTR_VALUE,
                             self.genFuncDict[valType](val).encode('utf-8'))
            self.root.appendChild(elm)

        # xmlテキストを生成
        return self.doc.toprettyxml(encoding=encode)
    
#
#
class FCPPrefHandler(ContentHandler):
    '''
    XMLからプリファレンスを復元するXMLパーサ用ハンドラ
    '''
    def __init__(self):
        '''
        初期化
        '''
        ContentHandler.__init__(self)

        # 展開結果を保持するための辞書
        self.prefDict = {}

        # 設定項目名(XMLタグ名)リスト
        # このリストに登録されていないタグは無視。
        self.tagNames = [PREF_KEY_SAVE_WINDW_LAYOUT,
                         PREF_KEY_WINDOW_POS,
                         PREF_KEY_WINDOW_SIZE,
                         PREF_KEY_DEFAULT_DIRECTORY,
                         PREF_KEY_KEEP_FILE_DIRECTORY,
                         PREF_KEY_VIEW_TREE_EXPANDED,
                         PREF_KEY_VIEW_DRAW_TREE_LINE,
                         PREF_KEY_VIEW_SHOW_TOOLBAR,
                         PREF_KEY_LIST_FILTER_EXTENSIONS]

        # XMLの属性(文字列)から設定項目ごとのデータ型に
        # 変換するための関数辞書
        self.convFuncDict = {PREF_VAL_TYPE_INT: lambda a: a,
                             PREF_VAL_TYPE_STRING: lambda a: a,
                             PREF_VAL_TYPE_BOOLEAN: lambda a: {'True': True, 'False': False}[a],
                             PREF_VAL_TYPE_TUPLE: lambda a: self.str_to_seq(a, types.TupleType),
                             PREF_VAL_TYPE_LIST: lambda a: self.str_to_seq(a, types.ListType)}

    def str_to_seq(self, s, type):
        '''
        カンマ区切りの文字列をリストまたはタプルに変換する。
        '''
        if s == None or len(s) == 0:
            return 

        # カンマで区切った文字列を整数値のリストに変換
        # 現状では整数のタプル以外は想定していない。
        valList = [int(token) for token in s.split(',')]
        if type == types.ListType:
            return valList
        elif type == types.TupleType:            
            return tuple(valList)

        return None
        
    def get_pref_dict(self):
        '''
        パース処理結果を取得
        '''
        return self.prefDict
    
    def startElement(self, name, attrs):
        '''
        開始タグが出現するたびにパーサから呼び出される関数
        '''
        if name not in self.tagNames:
            return
        # タグの属性(値のタイプ、値)を文字列として取得
        typeStr = attrs.getValue(ELEMENT_ATTR_VALUE_TYPE)
        val = attrs.getValue(ELEMENT_ATTR_VALUE)

        if typeStr in self.convFuncDict:
            # 値のタイプをもとに変換関数辞書で値を変換し、変換結果保持用
            # の辞書に追加する。
            self.prefDict[name] = {PREF_KEY_TYPE: typeStr,
                                   PREF_KEY_VALUE: self.convFuncDict[typeStr](val)}

#
#
class FCPPreference:
    '''
    pyFCPの動作オプションを保持するクラス。
    '''
    def __init__(self):
        '''
        初期化
        '''
        # プリファレンスデータは辞書として保持される。
        # 各設定項目は以下の形となっている。
        # {設定項目名: {値タイプキー: 値タイプ, 値キー: 値}}
        self.prefDict = {
            # ウインドウの座標、サイズを保存するかどうか
            PREF_KEY_SAVE_WINDW_LAYOUT:
            {PREF_KEY_TYPE: PREF_VAL_TYPE_BOOLEAN,
             PREF_KEY_VALUE: True},
            
            # ウインドウ左肩座標
            PREF_KEY_WINDOW_POS:
            {PREF_KEY_TYPE: PREF_VAL_TYPE_TUPLE,
             PREF_KEY_VALUE: (0, 0)},

            # ウインドウサイズ
            PREF_KEY_WINDOW_SIZE:
            {PREF_KEY_TYPE: PREF_VAL_TYPE_TUPLE,
             PREF_KEY_VALUE: (500, 400)},

            # ファイル選択ダイアログのデフォルトディレクトリ
            PREF_KEY_DEFAULT_DIRECTORY:
            {PREF_KEY_TYPE: PREF_VAL_TYPE_STRING,
             PREF_KEY_VALUE: './'},

            # ファイル選択ダイアログのディレクトリを保持
            PREF_KEY_KEEP_FILE_DIRECTORY:
            {PREF_KEY_TYPE: PREF_VAL_TYPE_BOOLEAN,
             PREF_KEY_VALUE: True},

            # ツリーを常に展開状態にするかどうか
            PREF_KEY_VIEW_TREE_EXPANDED:
            {PREF_KEY_TYPE: PREF_VAL_TYPE_BOOLEAN,
             PREF_KEY_VALUE: True},

            # ツリーにラインを描画するかどうか
            PREF_KEY_VIEW_DRAW_TREE_LINE:
            {PREF_KEY_TYPE: PREF_VAL_TYPE_BOOLEAN,
             PREF_KEY_VALUE: True},

            # ツールバーを表示するかどうか
            PREF_KEY_VIEW_SHOW_TOOLBAR:
            {PREF_KEY_TYPE: PREF_VAL_TYPE_BOOLEAN,
             PREF_KEY_VALUE: False},

            # フィルタ用拡張子リスト
            PREF_KEY_LIST_FILTER_EXTENSIONS:
            {PREF_KEY_TYPE: PREF_VAL_TYPE_LIST,
             PREF_KEY_VALUE: }}
    
    def get_value(self, key):
        '''
        設定値の取得
        '''
        if key not in self.prefDict:
            return None        
        return self.prefDict[key][PREF_KEY_VALUE]

    def set_value(self, key, value):
        '''
        値の設定
        '''
        if key not in self.prefDict:
            print 'unknown key'
            return

        typeStr = {
            types.IntType: PREF_VAL_TYPE_INT,
            types.StringType: PREF_VAL_TYPE_STRING,
            types.BooleanType: PREF_VAL_TYPE_BOOLEAN,
            types.TupleType: PREF_VAL_TYPE_TUPLE,
            types.ListType: PREF_VAL_TYPE_LIST
        }[type(value)]

        # 渡された値の型をチェック(不要かもしれないけど)
        if typeStr != self.prefDict[key][PREF_KEY_TYPE]:
            print 'value type is incorrect'
            return
        self.prefDict[key][PREF_KEY_VALUE] = value

    def set_xml_text(self, text):
        '''
        XMLから設定値の読み込み
        '''
        parser = xml.sax.make_parser()            
        handler = FCPPrefHandler()
        parser.setContentHandler(handler)
        try:
            parser.feed(text)
        except KeyError, err:
            print 'xml.sax KeyError : %s' % err.message

        self.prefDict = handler.get_pref_dict()
        
    def get_xml_text(self):
        '''
        設定値をXMLテキストとして書き出す
        '''
        gen = FCPPrefGenerator()
        text = gen.generate(self.prefDict)
        return text

#
# 以下はテスト用コード

#
#
def test_fcp_preference():
    '''
    プリファレンス生成テスト
    '''
    pref = FCPPreference()

    keyList = [(PREF_KEY_WINDOW_POS, (10, 10)),
               (PREF_KEY_WINDOW_SIZE, (100, 100)),
               (PREF_KEY_DEFAULT_DIRECTORY, '../../../'),
               (PREF_KEY_KEEP_FILE_DIRECTORY, True),
               (PREF_KEY_VIEW_TREE_EXPANDED, False),
               (PREF_KEY_VIEW_DRAW_TREE_LINE, False),
               (PREF_KEY_VIEW_SHOW_TOOLBAR, True),
               (PREF_KEY_LIST_FILTER_EXTENSIONS, ['.aaa', '.bbb'])]

    for k, v in keyList:
        print pref.get_value(k)

    for k, v in keyList:
        pref.set_value(k, v)

    for k, v in keyList:
        print pref.get_value(k)

#
#
def test_fcp_pref_xml():
    '''
    プリファレンスのデータをXMLへ変換するテスト
    '''
    pref = FCPPreference()
    gen = FCPPrefGenerator()

    xmlText = gen.generate(pref.prefDict)
    print xmlText

    return xmlText

#
#
def test_fcp_pref_handler(text):
    '''
    XMLからぷリファレンスデータを復元するテスト
    '''
    parser = xml.sax.make_parser()            
    handler = FCPPrefHandler()
    parser.setContentHandler(handler)
    try:
        parser.feed(text)

        prefDict = handler.get_pref_dict()        
        for k, v in prefDict.iteritems():
            print k, v
    except KeyError, err:
        print 'xml.sax KeyError : %s' % err.message
                             
#
#
if __name__ == '__main__':    
    import xml.sax

    test_fcp_preference()

    xmlText = test_fcp_pref_xml()

    test_fcp_pref_handler(xmlText)

上のソースコードは3つのクラスを含んでいます。

FCPPreference : オプション情報を保持させるためのクラス
FCPPrefHandler: XMLを読み込んでオプション情報を復元するためのクラス
FCPPrefGenerator: オプション情報をXMLテキストに変換するためのクラス

オプション情報→XML にはDOMのモジュールを使用し、
XML→オプション情報 にはSAXモジュールを使用しています。

今回はlambda式を沢山使っています。(あとで見直したときに、なんだっけね。。とならないようにせねば(笑))

ソースはあまりきれいではないかもしれませんが、今回は結構すっきり書けました。
設定項目が増えたりしても簡単に追加できそうです。

つづく。