はじめに
zig-clap
1 では、引数の構成情報(clap.Patam(Id)
を使用して、ヘルプ出力をするclap.help
関数が用意されている。
もともとが、ヘルプの最終出力となる文字列から引数の構成情報を抽出し、再度ヘルプ出力するような使い方が想定されているように見える。
そのため、静的に構成した引数情報をストリームパーザ(clap.streaming.Parser)用に用意した定義情報渡す場合には少し注意が必要となる。
-
help
関数は、CLI引数の説明文を取得するため、Id型にdescription(self: Id)
が存在することを要請している -
help
関数は、CLI引数が価を受ける場合の代替文字列を取得するため、Id型にvalue(self: Id)
が存在することを要請している-
---foo=VALUE
のVALUEの文字列のこと
-
したがって、clap
用のId型を定義する際、以下のような記述を求められる
pub const SomeId = enum {
a,
b,
c,
pub fn description(self: SomeId) []const u8 {
return switch (self) {
.a => "CLI引数aの説明文",
.b => "CLI引数bの説明文",
.c => "CLI引数cの説明文",
};
}
pub fn value(self: SomeId) []const u8 {
return switch (self) {
.a => "CLI引数a用",
.b => "CLI引数b用",
.c => "", // フラグ引数だと不要だから空文字列
};
}
}
毎度、description
関数とvalue
関数を書くのは面倒極まりない。
そこで手抜き方法を考えてみた。
方法
まず、説明文やvalue名をルックアップテーブルで引けるようstd.StaticStringMap
に移す。
const Dscriptions = std.StaticStringMap(struct {desc: []const u8, val: []const u8}).init(.{
.{@tagName(.a), .{.desc="CLI引数aの説明文", .val="CLI引数a用"}},
.{@tagName(.b), .{.desc="CLI引数bの説明文", .val="CLI引数b用"}},
.{@tagName(.c), .{.desc="CLI引数cの説明文", .val=""}},
});
ここではキーをEnumLiteralの文字列化しているが、
std.meta.EnumField`を受けるラッパーを用意すると、よりカターンゼン (型安全)となる。
上記のId型の定義は、以下のようになる
pub const SomeId = enum {
a,
b,
c,
pub fn description(self: SomeId) []const u8 {
return Descriptions.get(@tagName(self)).?.desc;
}
pub fn value(self: SomeId) []const u8 {
return Descriptions.get(@tagName(self)).?.val;
}
}
さらに、毎度定型文なdescription
やvalue
を書くのもめんどくさい。
この部分をヘルパ型として切り出し、usingnamespace
でmixin
することでめんどさを多少緩和できる。
const DescMap = std.StaticStringMap(struct {desc: []const u8, val: []const u8});
fn DescriptionHelper(comptime descs: DescMap) type {
return struct {
pub fn description(self: SomeId) []const u8 {
return descs.get(@tagName(self)).?.desc;
}
pub fn value(self: SomeId) []const u8 {
return descs.get(@tagName(self)).?.val;
}
};
}
これを使うと、Id定義は以下のように幾分見通しがよくなる。
pub const SomeId = enum {
a,
b,
c,
pub usingnamespace DescriptionHelper(Descriptions);
}
さらに一歩進んで
clap.help
のプロトタイプは、以下となっている。
pub fn help(
writer: anytype,
comptime Id: type,
params: []const Param(Id),
opt: HelpOptions)
出力先のwriter
はしょうがないとして、Id
とparams
はなんとかまとめたいのが人情である。
どうせId定義はclap
専用なので、params
の定義をIdに含めてしまっても別に構わんのだろ?ってことでId
を以下のように定義する。
pub const SomeId = enum {
a,
b,
c,
pub Decls: []const clap.Param(@This()) = &.{
.{.id = .a, ...}.
...
};
pub usingnamespace DescriptionHelper(Descriptions);
}
そうした上で、以下のようなラッパ関数を用意する。
pub fn help(writer: anytype, comptime Id: type, options: clap.HelpOptions) !void {
return clap.help(writer, Id, Id.Decls, options);
}
ヘルプオプションを決め打ち可能であれば、引数から落とすことでもっと単純化できる。
おまけ
ローカライズやらなんやらで、Id定義に説明文を直接持たせたくない場合、以下のようにする。
pub fn SomeId(comptime descs: DescMap) type {
enum {
a,
b,
c,
pub Decls: []const clap.Param(@This()) = &.{
.{.id = .a, ...}.
...
};
pub usingnamespace DescriptionHelper(descs);
};
}
clap.help
の呼び出して、解決させる。
try help(writer, Id(desc_i18n)); // desc_i18nはローカライズされた説明文
ストリームパース時は説明文は不要なため、以下のようにすればよいでしょう。
var parser = clap.streaming.Clap(Id(.{}), std.process.ArgIterator){
.params = ArgId(.{}).Decls,
.iter = iter,
};