💤

sleep の進捗をプログレスバーで表示するコマンドを Zig で作成する

2022/10/11に公開

課題

ひょんな事から Zig を学ぼうと思い、何か試しに作ってみようかなと思いましたが、これといって思いつかなかったので、以前 C で作ったコマンドライン引数に指定した秒数間 sleep し、進捗をプログレスバーで表示するコマンドを作成してみます。

解決策

勉強するしかないので、以下のドキュメント(+ zig のリポジトリ)を参考にしました。
https://ziglearn.org/
https://ziglang.org/documentation/master/

自分要件

1.コマンドライン引数が異常値の場合はエラーハンドリングする
2.test が書けるように関数を実装する

実装

エラーハンドリング

C のように main 関数に気軽に渡せる物と考えていたのですが、コマンドライン引数を取得する処理が分からず、公式ドキュメントを確認したどり着きました。

// コマンドライン引数のメモリ割当
const allocator = std.heap.page_allocator;
const args = try std.process.argsAlloc(allocator);
// メモリ解放
defer std.process.argsFree(allocator, args);

上記で割り当てた引数を利用するのですが、引数の数は args.len で取得できるので、引数が複数の場合はエラーとしメッセージを表示します。

if (args.len == 2) {
    // メインの処理...
} else {
    try std.io.getStdOut().writer().print("sleep 実施秒数を引数で入力してください。\nex: zleep 10", .{});
}

また、整数以外が入力された場合のエラーについてはは zig の if 文を利用する事で比較的簡単に、ハンドリングができました、parseInt が失敗した場合には else の処理を行います。

if (std.fmt.parseInt(u64, args[1], 10)) |seconds| {
    // parseInt 成功時は seconds に変換した整数型の値が代入される
// パースエラー時
} else |_| {
    try std.io.getStdOut().writer().print("引数には正の整数を入力してください。", .{});
}

if 文については公式ドキュメントにサンプルが大量にあったので助かりました、これで引数関連のエラーについては、一通りハンドリングできたかと思います。

test について

zig test で test が実行できるため、プログレスバーを表示する処理を関数化し、プログレスバーの表示数と、現在の sleep 実行割合を構造体メンバに代入し返却するようにしました。

test "get progress bar" {
    const s = try get_progress_bar(10, 20);

    var i: u64 = 0;
    var count: u64 = 0;
    while (i < s.progress_bar.len):(i += 1) {
        if (s.progress_bar[i] == '=') {
            count += 1;
        } else {
            continue;
        }
    }
    try std.testing.expect(s.sleep_percentage == 0.5);
    try std.testing.expect(count == 10);
}

Zig で sleep する zleep コマンドを作成しました、実行結果は以下のようになります、「=」の数が 20 を 100% としてプログレスバーを表示しています。

まとめ

完成したコードは以下に上げています。
https://github.com/gajirou/zleep

ハマった点としては、Zig では文字列は配列となり"a" ** 5のような表記で、同じ値を連結できるという事なので、プログレスバーの「=」は、以下の様に実装していました。

// 現在の sleep 秒数の割合を算出
const sleep_percentage = @intToFloat(f64, i) / @intToFloat(f64, seconds);
// プログレスバーの表示数を算出
const progress = @floatToInt(usize, sleep_percentage * 20);
// = を連結
const progress_bar = "=" ** progress;

この場合、progress 変数はerror: unable to evaluate constant expressionとなり、コンパイルは通りません、実行時のみ判明する値を利用しているためなのでおっしゃる通りではあるのですが、若干ハマりました。

これからもちょいちょい Zig を利用していこうと思います。

Discussion