目次

parseopt, parsecfg - Nimでの実行時設定

アプリケーションに実行時設定を渡す方法としては大きく分けて「コマンドライン引数」か「設定ファイル」の2つがある。

最近の言語なら、いずれも解析ライブラリが存在している。Nimもご多分に漏れず標準モジュールに存在する。

コマンドライン引数解析

parseopt

Pythonのargparseはとても細かく設定できて、パース前に「キーワードAはint型が来る前提で、デフォルト値は4で、BとCはいずれかが指定されていないとエラーにして」など設定した上でパースを実行する。

Nimの方はそこまで親切では無いが、とりあえずパースしてキーワードと値(あれば)をセットにするまでやってくれる。後はイテレートして適宜取得する感じ。必要な指定が無いとか重複してるとかのチェックは自分で実装する必要がある。

ただ、正直これなら最初から自分で実装しても大きくは変わらない……くらいの機能である点は否めない。

構文種別キー
--key=val
--key:val
cmdLongOptionkeyval
-kval
-k=val
-k:val
cmdShortOptionkval
--keycmdLongOptionkey““
-kcmdShortOptionk”“
keycmdArgumentkey”“
--key valcmdLongOption
+
cmdArgument
key
+
val
””
+
““

--key val」「-k val」式の文法はサポートして無くて、値なしのキーと位置引数に分けて解釈される。

import os, strutils, parseopt

# パラメータを格納する独自クラス
type
    Param* = object
        pos1*: string
        pos2*: string
        left*: bool
        right*: int
        debug*: int
var param = Param()

# コマンドラインパーサ
#   実験のため引数で与えているが、省略すると実際のコマンドライン引数が使われる
var p = initOptParser("pos1 --left --debug:3 -l -r=2 pos2")

# 位置引数の順番をカウント
var posCnt = 0

# イテレートして種別、キー、値を順次取得
for kind, key, val in p.getopt():
    case kind
    of cmdArgument:
        case posCnt
        of 0: param.pos1 = key
        of 1: param.pos2 = key
        else: discard
        posCnt += 1
    of cmdLongOption, cmdShortOption:
        case key
        of "left", "l": param.left = true
        of "right", "r": param.right = val.parseInt
        of "debug": param.debug = val.parseInt
    of cmdEnd: assert(false)  # cannot happen

echo $param

# =>
# (pos: "pon1", left: true, right: 2, debug: 3)

shortNoVal, longNoValを与えると、キーワードでないことを明言できる(ざっくりとした理解)……のだが、どうも目的や挙動がよくわからない。

docopt

こちらは第三者モジュール。本家はPythonだが、Nim移植版が存在する。

> nimble install docopt

docoptは「所定のルールに従ったヘルプを文字列で与えることで、パーサを組み立てる」という逆転の発想のライブラリ。

より複雑なルールを持ったパーサを、直感的にわかりやすい記法で作成できるので、標準モジュールのparseopt以上のことがしたくなったらこちらが楽。

設定ファイル解析

parsecfg

ini等でよくある、セクション毎に分かれた設定ファイルをパースできる。上記の公式解説に例もありわかりやすい。

[Section]
key1 = val1
key2: val2   ; Comment

import os, parsecfg, strutils

var
    cfg = loadConfig("settings.cfg")
    key1 = cfg.getSectionValue("Section", "key1")

echo key1  # => val

変数は使えない模様。

また、Windowsのドライブパスに含まれる「:」はキーと値の区切り文字として解釈されバグる恐れがあるため、全体を””でくくる必要がある。

さらに「\」はエスケープ文字として扱われるため、r“string”でエスケープ無効とするか、\\と2つ重ねる必要がある。

.ini記法のパーサは様々な言語に存在しても、この辺の細かな文字列の解釈の違いは言語毎に異なるため、言語毎に留意が必要。