LoginSignup
29
27

More than 3 years have passed since last update.

RustのCLIフレームワークを作ってみた

Last updated at Posted at 2020-02-28

作った経緯

RustのCLIフレームワークはclapが有名ですが機能が多く、そこまでの機能は必要じゃないけどCLIを簡単に構築できるライブラリをRustの勉強がてら作ってみました。

作ったCLIフレームワーク

A minimal CLI framework written in Rust

アイコンは開発を手伝って頂いている@rnitta さんが作ってくれました。
やはりコミットハッシュがキレイな人はセンスがありますね。

特徴

  • ミニマル
  • 難しい設定などはなく割と簡単に使える
  • 型(Bool, String, Int, Float)指定できるオプションフラグ
  • 依存クレートがない
  • アイコンがスゴく可愛い

Quick Start

最小限の使い方は以下の通りです。

$ cargo new cli_tool
$ cd cli_tool
$ echo 'seahorse = "0.6.2"' >> Cargo.toml
use std::env;
use seahorse::{App};

fn main() {
    let args: Vec<String> = env::args().collect();
    let app = App::new()
        .name(env!("CARGO_PKG_NAME"))
        .author(env!("CARGO_PKG_AUTHORS"))
        .version(env!("CARGO_PKG_VERSION"))
        .usage("cli_tool [args]")
        .action(|c| println!("Hello, {:?}", c.args));

    app.run(args);
}
$ cargo build --release
$ ./target/release/cli_tool --help
Name:
    cli_tool

Author:
    hogehoge <hogehoge@gmail.com>

Usage:
    cli_tool [args]

Version:
    0.1.0

$ ./target/release/cli_tool John
Hello, ["John"]

ActionとContext

Actionがコマンドの処理部分になります。
Actionの定義はContextを引数とした関数型です。

type Action = fn(Context);

ContextActionの引数でのみ使う型になります。
Contextはコマンドラインから受け取った引数からフラグとフラグの値を取り除いて残った配列をフィールドとして持ちます。
また、Contextは各フラグの値を取り出すためのメソッドを持ちます。

fn action(c: &Context) {
    println!("{:?}", c.args);
    println!("{}", c.bool_flag("bool");

    match c.string_flag("string") {
        Some(s) => println!("{}", s),
        None => println!("No string..."),
    }

    match c.int_flag("int") {
        Some(i) => println!("{}", i),
        None => println!("No integer..."),
    }

    match c.float_flag("float") {
        Some(f) => println!("{}", f),
        None => println!("No float..."),
    }
}

Flag

Flag--flag-fのようなコマンドラインでフラグを渡す場合に定義します。
フラグとして受け取る値の型はFlagTypeから選択できます。

enum FlagType {
    Bool,
    String,
    Int,
    Float,
}
use std::env;
use seahorse::{App, Context, Flag, FlagType};

fn main() {
    let args: Vec<String> = env::args().collect();
    let app = App::new()
        .name(env!("CARGO_PKG_NAME"))
        .author(env!("CARGO_PKG_AUTHORS"))
        .version(env!("CARGO_PKG_VERSION"))
        .usage("cli_tool [args]")
        .action(action)
        .flag(Flag::new("bye", "cli [arg] --bye", FlagType::Bool))
        .flag(Flag::new("age", "cli [arg] --age [age]", FlagType::Int));

    app.run(args);
}

fn action(c: &Context) {
    if c.bool_flag("bye") {
        println!("Bye, {:?}", c.args);
    } else {
        println!("Hello, {:?}", c.args);
    }

    if let Some(age) = c.int_flag("age") {
        println!("{:?} is {} years old.", c.args, age);
    }
}
$ cli_tool John --bye
Bye, ["John"]

$ cli_tool John --age 20
["John"] is 20 years old.

$ cli_tool John --bye --age=30
Bye, ["John"]
["John"] is 30 years old.

エイリアス

フラグにエイリアスを設定する場合はaliasメソッドをメソッドチェーンします。

let int_flag = Flag::new("integer", "cli [arg] --integer(-int, -i) [integer]", FlagType::Int)
    .alias("int")
    .alias("i");

Command

アクションを複数登録する場合はCommandを使います。

use seahorse::{App, Command, Context, Flag, FlagType};
use std::env;

fn main() {
    let args: Vec<String> = env::args().collect();
    let app = App::new()
        .name(env!("CARGO_PKG_NAME"))
        .author(env!("CARGO_PKG_AUTHORS"))
        .version(env!("CARGO_PKG_VERSION"))
        .usage("cli_tool [command] [x] [y]")
        .command(
            Command::new()
                .name("add")
                .usage("cli add [x] [y]")
                .action(add),
        )
        .command(
            Command::new()
                .name("sub")
                .usage("cli sub [x] [y]")
                .action(sub),
        );

    app.run(args);
}

fn add(c: &Context) {
    let x: i32 = c.args[0].parse().unwrap();
    let y: i32 = c.args[1].parse().unwrap();
    println!("{} + {} = {}", x, y, x + y);
}

fn sub(c: &Context) {
    let x: i32 = c.args[0].parse().unwrap();
    let y: i32 = c.args[1].parse().unwrap();
    println!("{} - {} = {}", x, y, x - y);
}
$ cli_tool add 10 3
10 + 3 = 13

$ cli_tool sub 34 10
34 - 10 = 24

また、CommandAppと同じようにflagメソッドでオプションフラグを設定することができます。

let command = Command::new()
    .name("hello")
    .usage("cli hello [arg]")
    .action(action)
    .flag(Flag::new("bye", "cli hello [arg] --bye", FlagType::Bool));

さいごに

ざっくりとした機能は以上になります。

issue, PR, Starお待ちしています!!
https://github.com/ksk001100/seahorse

29
27
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
29
27