俺々フォーマットからディクショナリを作る

ここでいう俺々フォーマットとは、好きな書式を自由に作れるというのではなくて、僕が勝手に決めたフォーマットからという意味です。なので汎用性はありません。


GAEで複数のTwitter Botを動かしていて、ほとんどの部分が共通しているから

ボットの名前、行動の名前、呼び出すメソッド、引数

こういう感じのエンティティをデータストアに保存しておいて、ボットの名前からクラスを、行動の名前からメソッドを呼び出す。その時に引数をディクショナリに変換して渡したい。というのが発端です。


具体的には引数文字列が、

source=hoge.txt,is_random,fmt="${item0}, ${item0}!",is_sustainable

こういう感じなら、hoge.txt(区切り文字で区切られたテキスト)をランダムに並び替えて(is_random)、重複しないように投稿した内容を記憶しながら(is_sustainable)、 fmt の形式(テンプレート)で Twitter に投稿する。というような感じで、 is_random を消せば順番に投稿するし、source を変えればつぶやく内容が変わる。フォーマットも自由に変えられる。と、こうしておけば単純な Bot ならプログラムを書かなくても良くなるはず。


単純なBotたち

こういうことがしたい

  • 文字列からディクショナリを作る。
  • それぞれの要素はカンマで区切られている。
  • KEY=VALUE という要素の場合は、「KEY: VALUE」のペアを作る。VALUEは空でもいい。
  • VALUEは文字列、クォートで囲む必要はない。
  • 要素がKEYだけの場合(=以下がない場合)は、「KEY: True」のペアを作る。
  • KEY, VALUEの前後のスペースは削除する。
  • VALUEにスペースやカンマを含める時は、シングルクォートかダブルクォートで囲む。(囲んだクォートは削除される。)

書いてみた

考えた方法は以下の二通り

  • トークン(KEY=VALUEのセット)を取得していく (re.match と m.group)
  • クォートされていないカンマで分割する (re.split)

カンマ区切りで分割する方法は試行錯誤したのですが、僕には出来ませんでした。”クォートされていないカンマ”という正規表現の書き方が分からなかった。トークンごとに取得する方法は、それなりに動いていて、上に書いた引数文字列なら

source="hoge.txt"
fmt="${item0}, ${item0}!"
is_sustainable="True"
is_random="True"

こういう感じのディクショナリが作成されます。



Pythonは名前付きキャプチャというのが使えて便利ですねー。

(?P<hogehoge>pattern)

と書けば、正規表現 pattern にマッチする要素が match.group("hogehoge") で取得できます。match.group("\1") よりも断然わかりやすい!

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import re


def eatup(line):
    rx_token = re.compile(r'''(?P<KEY>[a-zA-Z_]\w*)=(?P<VAL>("|').*?\3(?=\s*,)|[^,]*)|(?P<FLG>[a-zA-Z_]\w*)''')
    rx_quote = re.compile(r'''^(['"])(.*)\1$''')
    result = {}
    while line:
        m = rx_token.match(line)
        if m:
            if m.group("FLG"):
                result[m.group("FLG")] = True
            else:
                key   = m.group("KEY").strip()
                value = m.group("VAL").strip()
                result[key] = rx_quote.sub(r"\2", value)
            line = line[m.end():].lstrip()
        else:
            if line[0] != ",": raise SyntaxError, "invalid syntax"
            line = line[1:].lstrip()
    return result


if __name__ == '__main__':
    data = u"""source=hoge.txt,is_random,fmt="${item0}, ${item0}!",is_sustainable"""
    for k, v in eatup(data).iteritems():
        print '%s="%s"' % (k, v)