[Rust] dialoguerでインタラクティブな入力

rustで簡単にインタラクティブな標準入力ができる[dialoguer](https://github.com/mitsuhiko/dialoguer)を使ってみます．サンプルプログラム全体は[こちら](https://github.com/deepgreenAN/dialoguer_tutorial)にあります．

## Confirm
yes/noで答えられる質問を表示し，入力をboolで返します．yかnの入力ですぐ終了するため，yesやnoと入力してエンターで確定したい場合は後述する`Input`を使うのがいいと思います．
[以下の例](https://github.com/deepgreenAN/dialoguer_tutorial/blob/master/src/bin/confirm.rs)ではyを入力するたびに"プログラムを続けます．"が表示されます．
```rust
use dialoguer::Confirm;

'main: loop {
	if Confirm::new()
		.with_prompt("プログラムを続けますか?")
		.default(true)
		.interact()? // Result<bool, std::io::Error>
	{
		println!("プログラムを続けます．");
	} else {
		println!("プログラムを終了します．");
		break 'main;
	}
}
```
コードを実行すると
```
プログラムを続けますか? [Y/n]
```
のように表示され，yを入力すれば
```
プログラムを続けますか? yes
プログラムを続けます．
プログラムを続けますか? [Y/n]
```
nを入力すれば
```
プログラムを続けますか? no
プログラムを終了します．
```
となります．

## Input
質問を表示し，エンターで確定するまでの入力を受け取ります．
[以下の例](https://github.com/deepgreenAN/dialoguer_tutorial/blob/master/src/bin/easy_input.rs)では入力した名前を表示します．プレースホルダの初期の値として"name"が入っています．

```rust
use dialoguer::Input;

let input = Input::<String>::new()
	.with_prompt("名前を入力してください")
	.with_initial_text("name")
	.interact()?;

println!("ようこそ！{}さん", input);
```
コードを実行すると
```
名前を入力してください: name
```
のように表示され，名前を入力すると
```
名前を入力してください: 山田太郎
ようこそ！山田太郎さん
```
と表示されます．空の状態を許容していないため名前を入力しないと終了しません．

`Input`では入力値の一次保存とバリデーションができます．
入力値の一時保存には`dialoguer::History`トレイトを実装した型を利用することができます．FIFOを利用することで新しいものから順に履歴を取得できます．
```rust
use dialoguer::History; // featuresにhistoryを指定
use std::collections::VecDeque;

struct MyHistory(VecDeque<String>);

impl MyHistory {
    fn new() -> Self {
        Self(VecDeque::new())
    }
}

impl<T: ToString> History<T> for MyHistory {
    fn read(&self, pos: usize) -> Option<String> {
        self.0.get(pos).cloned()
    }
    fn write(&mut self, val: &T) {
        self.0.push_front(val.to_string());
    }
}
```
[以下の例](https://github.com/deepgreenAN/dialoguer_tutorial/blob/master/src/bin/complex_input.rs)では入力した名前の履歴としての保存と文字数によるバリデーションを行っています．`Input::validate_with`に与えるバリデーションを行うクロージャ―では`()`を返す`Result`型を返すようにします．このクロージャ―には引数と返り値に型注釈をする必要があります．また，`History`を使う場合は`Input::interact`は使えず，変わりに`Input::interact_text`を使います．`interact_text`は日本語を入力すると表示が崩れることがありますが，問題なく入力できます．
名前として"exit"を入力することでループを終了します．

```rust
fn main() -> Result<(), std::io::Error> {
    let mut history = MyHistory::new();
    'main: loop {
        let input = Input::<String>::new()
            .with_prompt("あなたの名前を入力してください")
            .validate_with(|input: &String| -> Result<(), &str> {
                if input.chars().count() <= 10 {
                    Ok(())
                } else {
                    Err("名前は10文字以内にしてください．")
                }
            })
            .history_with(&mut history)
            .interact_text()?;

        if &input == "exit" {
            break 'main;
        }
        println!("ようこそ！{}", input);
    }

    Ok(())
}
```

以下のように入力した場合
```
あなたの名前を入力してください: Smith
ようこそ！Smith
あなたの名前を入力してください: Johnson
ようこそ！Johnson
あなたの名前を入力してください: Williams
ようこそ！Williams
```
その次の入力では上下キーで履歴を参照できます．
```
あなたの名前を入力してください: Williams
```

一方で
```
あなたの名前を入力してください: JohnsonSmithWilliams
```
のように文字数をオーバーした場合は
```
error: 名前は10文字以内にしてください．
あなたの名前を入力してください:
```
のようにエラーとなり再び入力を待ちます．

## Password
基本は`Input`と同じですが，確認のための再入力とそれが失敗したときに表示するエラーを指定できます．パスワードは入力中も表示されません．

```rust
use dialoguer::Password;

let password = Password::new()
	.with_prompt("作成するパスワードを入力してください")
	.with_confirmation("再び入力してください", "パスワードが違います")
	.interact()?;

println!("{}", password);
```
コードを実行すると
```
作成するパスワードを入力してください:
```
と表示され，入力すると再入力を求められます．
```
再び入力してください:
```
再入力に成功した場合はパスワードが表示され(例のプログラムの場合)，失敗した場合は
```
error: パスワードが違います
作成するパスワードを入力してください:
```
のようにエラーが表示されて最初に戻ります．

## Select
選択肢を矢印キーで選択できます．
[以下の例](https://github.com/deepgreenAN/dialoguer_tutorial/blob/master/src/bin/select.rs)では選択肢の言語について，確定した選択肢に対応するインデックスが返り，挨拶が表示されます．
```rust
use dialoguer::Select;

let choices = vec!["English", "日本語", "中文"];

let selection = Select::new().items(&choices).interact()?;

match selection {
	0_usize => println!("Hello!"),
	1_usize => println!("こんにちは！"),
	2_usize => println!("你好!"),
	_ => println!("undefined selection"),
}
```
コードを実行すると
```
> English
  日本語
  中文
```
のように表示され，矢印キーで選択を変更・エンターで確定すると
```rust
こんにちは！
```
挨拶が表示されます．

# MultiSelect
選択肢を複数選択できます．
[以下の例](https://github.com/deepgreenAN/dialoguer_tutorial/blob/master/src/bin/multi_select.rs)では選択肢のタグについてデフォルトを指定し，選択されたタグを全て表示しています．
```rust
use dialoguer::MultiSelect;

let choices = vec!["プログラミング", "Rust", "Python", "数学", "歴史"];
let defaults = vec![true, false, false, false, false];

let selections: Vec<usize> = MultiSelect::new()
	.with_prompt("タグを選んでください")
	.items(&choices)
	.defaults(&defaults)
	.interact()?;

let tags = selections.iter().map(|i| choices[*i]).collect::<Vec<_>>();
println!("タグは{:?}です．", tags);
```
コードを実行すると
```
タグを選んでください:
> [x] プログラミング
  [ ] Rust
  [ ] Python
  [ ] 数学
  [ ] 歴史
```
のように選択肢が出るため，矢印キーで変更・スペースキーで選択・エンターで確定すると
```
タグを選んでください: プログラミング, Rust, 数学
タグは["プログラミング", "Rust", "数学"]です．
```
選択されたタグが全て表示されます．

## FuzzySelect
選択肢を入力によって絞り込むことができます．
[以下の例](https://github.com/deepgreenAN/dialoguer_tutorial/blob/master/src/bin/fuzzy_select.rs)ではプログラミング言語の選択肢を絞り込み，選択した言語を表示します．
```rust
use dialoguer::FuzzySelect; // featuresでfuzzy-selectを指定

let selections = vec![
	"ActionScript",
	"AppleScript",
	"ASP.NET",
	"Assembly",
	"BASIC",
	"Brainf**k",
	"C",
	"C#",
	"C++",
	...省略
];

let selection = FuzzySelect::new()
	.with_prompt("言語を選択してください")
	.items(&selections)
	.interact()?;

println!("{}が選択されました", selections[selection]);
```
コードを実行すると
```
言語を選択してください |
  ActionScript
  AppleScript
  ASP.NET
  Assembly
  BASIC
  Brainf**k
  C
  C#
  C++
  ...省略
```
のように選択肢が表示され，"C"と入力すると
```
言語を選択してください C|
> C
  C#
  C++
  COBOL
  CoffeeScript
  Common Lisp
  CSS
  CUE
  Objective-C
  Objective-C++
  BASIC
  OCaml
  SCSS
```
のように選択肢が絞り込まれます．矢印で変更・エンターで確定をすると
```
言語を選択してください: C++
C++が選択されました
```
選択した言語が表示されます．

## Sort
リストのソートができます．
[以下の例]()では複数のコマンドのリストをソートしてその順番に表示します．
```rust
use dialoguer::Sort;

let list = vec!["command A", "command B", "command C", "command D"];
let sorted: Vec<usize> = Sort::new()
	.with_prompt("実行する順序を選んでください")
	.items(&list)
	.interact()?;

for command in sorted.into_iter().map(|i| list[i]) {
	println!("{}を実行しました", command);
}
```
コードを実行すると
```
実行する順序を選んでください:
> [ ] command A
  [ ] command B
  [ ] command C
  [ ] command D
```
のように選択肢として表示されるため，スペースキーで選択・矢印キーで移動・エンターで確定すると
```
実行する順序を選んでください: command B, command A, command D, command C
command Bを実行しました
command Aを実行しました
command Dを実行しました
command Cを実行しました
```
ソートした順番に表示されます．

紹介した以外でもテーマを設定したり，[console](https://github.com/console-rs/console)クレートと連携したりできるようです．