+++ date = "2020-05-12JST" tags = ["rust"] title = "rustで作るcli tool" slug = "rust" +++ 私は、cli(command line interface) toolを好んで使うのですが、自分で作ることもあります。 特に、ワンバイナリなtoolは、CIやdockerを回すときに便利でgolangをよく使います。goはshellとの親和性が高いので色々な場所で扱いやすく、非常に素晴らしい言語です。 ただ、最近は、rustが人気みたい。理由としては、高速で、サイズが小さく、安全であることが挙げられます。デメリットはコンパイルが遅いこと。 しかし、rustも非常に魅力なので、`base64 {encode,decode}`などをcli tool化しながら、rustを学んでいこうという記事です。 まず、テンプレを作成します。`src/main.rs`が本体になります。 ```sh $ cargo new udrs $ cd udrs $ vim src/main.rs ``` ここで、cli optionなどを書きやすくするframeworkの[seahorse](https://github.com/ksk001100/seahorse)を導入します。 ```rust:src/main.rs use std::env; use seahorse::{App, Command, Context}; fn main() { let args: Vec = 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); } ``` ```toml:Cargo.toml [dependencies] seahorse = "0.7.1" ``` では、実行してみましょう。ここでは、オプションを`t`で指定しています。 ```sh $ cargo run t hello world. ``` 次に、引数をbase64でencode, decodeするオプションを実装してみようと思います。 ```toml:Cargo.toml [dependencies] base64 = "0.9.2" ``` ```rust:src/main.rs use std::env; use seahorse::{App, Command, Context}; fn main() { let args: Vec = 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::(); println!("{:?}",res); } ``` これで、`e,d`オプションが追加されました。base64でencodeしてみましょう。 ```sh $ cargo run e "hello world." aGVsbG8gd29ybGQu ``` 次は、decodeしてみます。 ```sh $ cargo run d "aGVsbG8gd29ybGQu" hello world. ``` うまくいきました。 testも追加してみましょう。 ```rust:src/main.rs ... #[cfg(test)] mod tests { #[test] fn base64_encode() { let expected = "aGVsbG8gd29ybGQu"; let actual = base64::encode("hello world."); assert_eq!(expected, actual); } } ``` ```sh $ cargo test test tests::base64_encode ... ok ``` 面倒なコマンドは、makefile化することもできます。 ```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 . ``` ```sh $ make run help ``` runだけでなく、buildしてコマンドの挙動を確認してみましょう。 ```sh $ cargo build $ ./target/debug/udrs ``` バイナリの配布は、travis-ciでbuildして、github releasesにdeployします。 ```yml:.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して以下のコマンドを実行。 ```sh $ sudo gem i travis $ travis login $ travis encrypt $GITHUB_ACCESS_TOKEN $ cat .travis.yml deploy: api_key: secure: "xxx" ``` tagをpushすると、travisが走ります。 ```sh $ git add . $ git commit -m "first" $ git push origin master $ git tag -a "0.1.0" -m "v0.1.0" $ git push origin --tags ``` https://github.com/syui/udrs https://qiita.com/syui/items/e071ba72ea82d583e380