0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

zig-clapでストリーミングパース時のヘルプ表示支援

Posted at

はじめに

zig-clap1 では、引数の構成情報(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;
    }
}

さらに、毎度定型文なdescriptionvalueを書くのもめんどくさい。

この部分をヘルパ型として切り出し、usingnamespacemixinすることでめんどさを多少緩和できる。

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はしょうがないとして、Idparamsはなんとかまとめたいのが人情である。

どうせ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,
};
  1. https://github.com/Hejsil/zig-clap

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?