Open9
Rust 初心者が CLI 作る時のメモ

Cargo のコマンド
Project の初期化
cargo init
# or
cargo new
cargo init
と cargo new
の違い
他 Create の追加
cargo add reqwest
# 特定の機能を有効にする場合
cargo add reqwest -F json
ビルドとローカルへのインストール
cargo build
cargo install --path .
フォーマットとテスト実行
cargo fmt
cargo test

Reqwest
cargo add tokio -F full
が必要
use std::collections::HashMap;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let resp = reqwest::get("https://httpbin.org/ip")
.await?
.json::<HashMap<String, String>>()
.await?;
println!("{resp:#?}");
Ok(())
}
tokio は以下の説明を確認
Proxy 設定
Proxy の basic 認証も可能
let proxy = reqwest::Proxy::https("http://localhost:1234")?
.basic_auth("Aladdin", "open sesame");
ファイルダウンロード
Cookie 認証
cookie_store を使う
fn create_proxy(c: &Config) -> reqwest::Result<Proxy> {
let proxy = Proxy::https(&c.proxy_url)?.basic_auth(&c.proxy_user, &c.proxy_pass);
Ok(proxy)
}
pub(crate) fn build_client(c: &Config) -> reqwest::Result<reqwest::Client> {
let cookie_store = Arc::new(Jar::default());
let proxy = create_proxy(c)?;
let client = reqwest::Client::builder()
.proxy(proxy)
.cookie_store(true)
.cookie_provider(Arc::clone(&cookie_store))
.build()?;
Ok(client)

serde
derive
を使いたいので追加しておく
cargo add serde -F derive
serialize と deserialize が簡単にできる
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let point = Point { x: 1, y: 2 };
// Convert the Point to a JSON string.
let serialized = serde_json::to_string(&point).unwrap();
// Prints serialized = {"x":1,"y":2}
println!("serialized = {}", serialized);
// Convert the JSON string back to a Point.
let deserialized: Point = serde_json::from_str(&serialized).unwrap();
// Prints deserialized = Point { x: 1, y: 2 }
println!("deserialized = {:?}", deserialized);
}
rename や skip
#[derive(Serialize)]
struct Resource {
// Always serialized.
name: String,
// Never serialized.
#[serde(skip_serializing)]
hash: String,
// Use a method to decide whether the field should be skipped.
#[serde(skip_serializing_if = "Map::is_empty")]
metadata: Map<String, String>,
#[serde(rename="camelCase")]
camel_case: String,
}

seahorse
コマンド や サブコマンド等を簡単に作れる
use seahorse::{App};
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
let app = App::new(env!("CARGO_PKG_NAME"))
.description(env!("CARGO_PKG_DESCRIPTION"))
.author(env!("CARGO_PKG_AUTHORS"))
.version(env!("CARGO_PKG_VERSION"))
.usage("cli [args]")
.action(|c| println!("Hello, {:?}", c.args));
app.run(args);
}

dialoguer
Input
一問一答できる
use dialoguer::Input;
fn main() {
let name: String = Input::new()
.with_prompt("Your name?")
.interact_text()
.unwrap();
println!("Your name is: {}", name);
}
MultiSelect
あらかじめ items を用意しておいて、複数選択可能
use dialoguer::MultiSelect;
fn main() {
let items = vec!["foo", "bar", "baz"];
let selection = MultiSelect::new()
.with_prompt("What do you choose?")
.items(&items)
.interact()
.unwrap();
println!("You chose:");
for i in selection {
println!("{}", items[i]);
}
}
FuzzySelect
曖昧検索できる。ただし、選べるのは一つ
use dialoguer::FuzzySelect;
fn main() {
let items = vec!["foo", "bar", "baz"];
let selection = FuzzySelect::new()
.with_prompt("What do you choose?")
.items(&items)
.interact()
.unwrap();
println!("You chose: {}", items[selection]);
}

skim
曖昧検索と複数選択も可能
github の example
利用可能キーとアクションとデフォルトのキーバインディング
TODO: 追記
indicatif
Progress Bar とかとか

strum
以下を参考に enum で String 可能にする derive とかとか

thiserror
外部 crate のエラーとかどうしようと思って利用
use thiserror::Error;
#[derive(Error, Debug)]
pub enum DataStoreError {
#[error("data store disconnected")]
Disconnect(#[from] io::Error),
#[error("the data for key `{0}` is not available")]
Redaction(String),
#[error("invalid header (expected {expected:?}, found {found:?})")]
InvalidHeader {
expected: String,
found: String,
},
#[error("unknown data store error")]
Unknown,
}
以下のようにすることが可能
#[error("xx error")]
LibraryError(#[from] xx::Error)