LoginSignup
48
49

More than 3 years have passed since last update.

rustで作るcli tool

Last updated at Posted at 2020-05-12

私は、cli(command line interface) toolを好んで使うのですが、自分で作ることもあります。

特に、ワンバイナリなtoolは、CIやdockerを回すときに便利でgolangをよく使います。goはshellとの親和性が高いので色々な場所で扱いやすく、非常に素晴らしい言語です。

ただ、最近は、rustが人気みたい。理由としては、高速で、サイズが小さく、安全であることが挙げられます。デメリットはコンパイルが遅いこと。

しかし、rustも非常に魅力なので、base64 {encode,decode}などをcli tool化しながら、rustを学んでいこうという記事です。

まず、テンプレを作成します。src/main.rsが本体になります。

$ cargo new udrs
$ cd udrs
$ vim src/main.rs

ここで、cli optionなどを書きやすくするframeworkのseahorseを導入します。

src/main.rs
use std::env;
use seahorse::{App, Command, Context};

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("t")
                .usage("udrs t {}")
                .action(t),
                );
    app.run(args);
}

fn t(_c: &Context) {
    let t = "hello world.";
    println!("{}",t);
}
Cargo.toml
[dependencies]
seahorse = "0.7.1"

では、実行してみましょう。ここでは、オプションをtで指定しています。

$ cargo run t
hello world.

次に、引数をbase64でencode, decodeするオプションを実装してみようと思います。

Cargo.toml
[dependencies]
base64 = "0.9.2"
src/main.rs
use std::env;
use seahorse::{App, Command, Context};

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("t")
            .usage("udrs t {}")
            .action(t),
            )
        .command(
            Command::new()
            .name("e")
            .usage("udrs e {}")
            .action(e),
            )
        .command(
            Command::new()
            .name("d")
            .usage("udrs d {}")
            .action(d),
            );
    app.run(args);
}

fn t(_c: &Context) {
    let t = "hello world.";
    println!("{}",t);
}

fn e(c: &Context) {
    println!("{}", base64::encode(&c.args[0]));
}

//fn d(c: &Context) {
//    println!("{:?}", &base64::decode(&c.args[0]).unwrap());
//}
fn d(c: &Context) {
    let by = base64::decode(&c.args[0]).unwrap();
    let res = by.iter().map(|&s| s as char).collect::<String>();
    println!("{:?}",res);
}

これで、e,dオプションが追加されました。base64でencodeしてみましょう。

$ cargo run e "hello world."
aGVsbG8gd29ybGQu

次は、decodeしてみます。

$ cargo run d "aGVsbG8gd29ybGQu"
hello world.

うまくいきました。

testも追加してみましょう。

src/main.rs
...

#[cfg(test)]
mod tests {
    #[test]
    fn base64_encode() {
        let expected = "aGVsbG8gd29ybGQu";
        let actual = base64::encode("hello world.");
        assert_eq!(expected, actual);
    }
}
$ cargo test
test tests::base64_encode ... ok

面倒なコマンドは、makefile化することもできます。

LOG_LEVEL := debug
APP_ARGS  := "hello world."
export RUST_LOG=url=$(LOG_LEVEL)
PREFIX := $(HOME)/.cargo

run:
    cargo run $(APP_ARGS)

test:
    cargo test

check:
    cargo check $(OPTION)

install:
    cargo install --force --root $(PREFIX) --path .
$ make run
help

runだけでなく、buildしてコマンドの挙動を確認してみましょう。

$ cargo build
$ ./target/debug/udrs

バイナリの配布は、travis-ciでbuildして、github releasesにdeployします。

.travis.yml
dist: trusty
language: rust
services: docker
rust:
  - stable
sudo: required
env:
  global:
    - NAME=udrs

matrix:
  include:
    - env: TARGET=x86_64-unknown-linux-musl
    - env: TARGET=x86_64-apple-darwin
      os: osx
    - env: TARGET=x86_64-pc-windows-gnu

before_install:
  - rustup self update

install:
  - source ~/.cargo/env
  - cargo install --force cross

script:
  - cross test --target $TARGET --release

before_deploy:
  - cross build --target $TARGET --release
  - bin=$NAME
  - if [[ $TARGET = "x86_64-pc-windows-gnu" ]]; then bin=$NAME.exe; fi
  - tar czf $NAME-$TRAVIS_TAG-$TARGET.tar.gz -C target/$TARGET/release $bin

deploy:
  api_key:
    secure: "xxx"
  file_glob: true
  file: $NAME-$TRAVIS_TAG-$TARGET.*
  on:
    tags: true
  provider: releases
  skip_cleanup: true

cache: cargo
before_cache:
  - chmod -R a+r $HOME/.cargo

branches:
  only:
    - /^v?\d+\.\d+\.\d+.*$/
    - master

secure: "xxx"は、travisコマンドで発行します。github-access-tokenをgetして以下のコマンドを実行。

$ sudo gem i travis
$ travis login
$ travis encrypt $GITHUB_ACCESS_TOKEN

$ cat .travis.yml
deploy:
  api_key:
    secure: "xxx"

tagをpushすると、travisが走ります。

$ git add .
$ git commit -m "first"
$ git push origin master

$ git tag -a "0.1.0" -m "v0.1.0"
$ git push origin --tags

48
49
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
48
49