5.6 KiB
+++ date = "2020-05-12" 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
が本体になります。
$ cargo new udrs
$ cd udrs
$ vim src/main.rs
ここで、cli optionなどを書きやすくするframeworkのseahorseを導入します。
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);
}
[dependencies]
seahorse = "0.7.1"
では、実行してみましょう。ここでは、オプションをt
で指定しています。
$ cargo run t
hello world.
次に、引数をbase64でencode, decodeするオプションを実装してみようと思います。
[dependencies]
base64 = "0.9.2"
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も追加してみましょう。
...
#[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します。
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