文字列変換テーブルの UI

文字列テーブルデータ変換処理ツールはコマンドラインから行う物としてユーザーインターフェースを作成します。
当然 CUI ですので、どんなコマンドラインオプションを用意するかを考えます。
まず、このツールは文字列テーブルの csv ファイルを読み込んで、バイナリデータとラベルを列挙した
C/C++ のヘッダファイルを出力します。また、文字列テーブルには複数の言語が含まれているので、
どれを使用するかの指定をしなければなりません。
以上を踏まえて、コマンドラインオプションの説明も兼ねてツールの使用方法を表示する以下の関数を定義します。

# 使い方
def usage():
  print """>python strtblcnv.py [-i] infile [option]
  [-i] infile ..... 入力ファイル名
  [option]
  -o outfile ...... 出力ファイル名(省略時は infile の拡張子を .stbl に変更した
                    ファイル名を使用)
  -h headerfile ... 出力ヘッダファイル名(省略時は infile の拡張子を .h に
                    変更したファイル名を使用)
  -l lang ......... 言語の指定(省略時には日本語を指定)
             jp ... 日本語
             en ... 英語
             fr ... フランス語
             ge ... ドイツ語
             sp ... スペイン語
             ne ... オランダ語
             it ... イタリア語
                    * 上記以外を指定した場合にはその文字列をそのまま
                      言語指定の文字列として使用
"""

コマンドラインオプションは -N param という書式で -N がパラメータの指定、
param がパラメータの値となっています。入力ファイル名のみ、パラメータ指定は省略可能です。
これは、コマンドラインから単純に strtblcnv.py infile とすれば変換が出来るようにするためです。


プログラムではコマンドラインオプションを解析し、指示された内容にしたがって処理を行います。
変換処理自体は前回のテストでも行ったように、既に用意してある関数を順に呼び出せば OK です。

# 変換処理実行
def main():
  # プログラムの引数を解析
  # 必要なパラメータがそろっているかチェック
  # if 必要なパラメータがそろっていなければ
    # 使い方を表示
    # プログラムを終了する
  # else
    # ファイルから入力データを取得
    # if 入力データが取り出せなかったら
      # エラー表示
      # エラーを返して終了
    # end if
    # 文字列データのオフセットテーブルを作成
    # ファイルを出力
    # ヘッダファイルを出力
  # end if

まず、プログラムの引数を解析します。プログラムのコマンドラインオプションは sys.argv に格納されていますが、
先頭はスクリプトファイル名です。よって二つ目以降を取り出す必要があります。

  # プログラムの引数を解析
  # for スクリプト名を除くコマンドラインオプションの各要素ごとに
  for a in sys.argv[1:]:

各パラメータは、パラメータ指定の文字列をキーとする辞書に保持することにします。
コマンドラインオプションは -N の形のパラメータ指定とそれに続くパラメータの値の組の連続です。
そこで、 sys.argv から取り出した文字列の先頭が "-" ならばそれをキーとして保持し、そうでなければ
現在保持しているキーに対応する値として辞書に登録します。ただし、入力ファイルのみはパラメータ指定の
省略が可能です。そこで、デフォルトのキーを入力ファイルの "-i" としておき、辞書に値を登録したら
現在のキーを "-i" に戻すようにします。こうしておくことで、 -o outfile infile といった並びに対応できます。

  # プログラムの引数を解析
  # パラメータの辞書を初期化
  params={}
  # デフォルトのキーを入力ファイル名のパラメータ指定で初期化
  opt_infile="-i"
  opt=opt_infile
  # for スクリプト名を除くコマンドラインオプションの各要素ごとに
  for a in sys.argv[1:]:
    # if 文字列の先頭が "-" ならば
    if a[0]=="-":
      # キーとして保持
      opt=a
    # else
    else:
      # 現在のキーに対応する値として辞書に登録
      params[opt]=a
      # 現在のキーを入力ファイル名のパラメータ指定に戻す
      opt=opt_infile

次にパラメータのチェックです。

  # 必要なパラメータがそろっているかチェック
  # 入力ファイル名があるかどうか
  ok=params.has_key("-i")
  # if 入力ファイル名があるならば
  if ok:
    infile=params["-i"]
    # if 出力ファイル名がなければ
    if not params.has_key("-o"):
      # 入力ファイル名の拡張子を ".stbl" で置き換える
      params["-o"]=infile[:infile.rindex(".")]+".stbl"
    # if 出力ヘッダファイル名がなければ
    if not params.has_key("-h"):
      # 入力ファイル名の拡張子を ".h" で置き換える
      params["-h"]=infile[:infile.rindex(".")]+".h"
    # if 言語指定が無ければ
    if not params.has_key("-l"):
      # 言語指定を日本語にする
      params["-l"]="日本語"
    # else if 指定の値が言語指定辞書のキーならば
    elif LANG_TABLE.has_key(params["-l"]):
      # テーブルから言語指定文字列を取り出す
      params["-l"]=LANG_TABLE[params["-l"]]

必須なのは入力ファイル名指定のみです。出力のそれぞれのファイル名は、指定が無ければ入力ファイル名の
拡張子を差し替えて用意します。言語指定は半角英小文字二文字による指定を csv に含まれている(であろう)
言語をあらわす文字列に置き換えています。 csv では日本語文字列を使用していますがコマンドラインから
直接日本語文字列を入力するのは面倒だろうということでこのようにしています。 csv や元となるスプレッド
シートでデータを作るときにはこの仕様に注意する必要があり、少し自由度が低いかもしれません。
自分のかかわる範囲内で使うだけならそんなに問題ではありませんが。
あと、言語指定文字列の辞書というのが出てきましたのでこれを事前に定義するようにしてやります。

LANG_TABLE={
"jp":"日本語",
"en":"英語",
"fr":"フランス語",
"ge":"ドイツ語",
"sp":"スペイン語",
"ne":"オランダ語",
"it":"イタリア語"
}

必要なパラメータが無ければ使い方を表示してプログラムを終了します。

  # if 必要なパラメータがそろっていなければ
  if not ok:
    # 使い方を表示
    usage()
    # プログラムを終了する
    sys.exit(1)

バッチファイルから実行したりする場合のことを考えて、失敗したときには 1 を返すようにしておきます。
また、本当は if ok: 〜 else: の方が流れとしては良いといわれているのですが、そうすると if と else の
間が空きすぎてかえって流れが分かりにくくなる気がしたのでこの順序にしています。

  # else
  else:
    # ファイルから入力データを取得
    data=input_from_csv(params["-i"], params["-l"])
    # if 入力データが取り出せなかったら
    if data==None:
      # エラー表示
      print "データが取り出せませんでした"
      # エラーを返して終了
      sys.exit(1)
    # end if
    # 文字列データのオフセットテーブルを作成
    table=offset_from_data(data)
    try:
      # ファイルを出力
      output_string_table(params["-o"], data, table)
      # ヘッダファイルを出力
      output_label_enum(params["-h"],data,"enum {\n","\n};")
    except IOError:
      sys.exit(1)
  # end if

最後に用意されたパラメータを使用してファイルを読み込み、加工、出力しています。
エラーが発生した場合には 1 を返します。


これでとりあえず csv → バイナリデータの文字列テーブル変換処理は出来上がりです。
自分のプログラミングの過程を文章の形で公開・記録してみようと思ってやってみましたが
やはり難しかったです。本当に全部記録したらものすごい量になりますしね。
しかし、コメントで指摘を頂けたのは大きかったように思います。