インポートとくればエクスポートです。
インポートと違い、エクスポートはファイルの書き込み失敗のおそれがあるのでエラー処理が必要です。
Rustでファイルに書き出す処理はオプションがいくつかあるようで、それについて調べるために下記を実験してみました。
// write_file_create
use std::io::Write;
// NOTE: 事前に下記コマンドでファイルを用意しておく
// echo "01234567890123456789" > ./test.data
// main 関数
#[rustfmt::skip]
fn main() -> Result<(), std::io::Error> {
let path = "./test.data";
let mut file = std::fs::File::create(path)?; // 上書き+余分なデータ削除
// let mut file = std::fs::OpenOptions::new().write(true).open(path)?; // 上書き+余分なデータは残る
// let mut file = std::fs::OpenOptions::new().write(true).create(true).open(path)?; // 上書き+余分なデータは残る
// let mut file = std::fs::OpenOptions::new().write(true).truncate(true).create(true).open(path)?; // 上書き+余分なデータ削除
// let mut file = std::fs::OpenOptions::new().write(true).create(true).truncate(true).open(path)?; // 上書き+余分なデータ削除
let _ = file.write_all(b"test write create")?;
Ok(())
}
std::fs::OpenOptions
を使う場合、ファイルに対する処理の仕方が異なってきます。
まず、前提として
echo "01234567890123456789" > ./test.data
std::fs::OpenOptions::new().write(true).open(path)?;
の場合は、ファイルが存在しないとエラーとなります。
また、ファイルが存在する場合は書き込みますが、
test write create789
となり、書き込みデータの分だけ上書きされてファイル全体での処理とはなりません。
ファイルが存在しない場合に書き込みできるようにするには、create(true)
を追加すると、新規で作成するようになります。
ファイル全体で上書きしたい、余計なデータが残らないようにするには、truncate(true)
を追加することでエクスポートした内容がそのままファイルの内容となります。
std::fs::File::create(path)
を使うとcreate(true)
とtruncate(true)
も指定された状態になります。
ということで、ファイルのエクスポート処理は最終的に下記のようにしました。
pub(crate) fn export_to(&self, path: &String) -> Result<(), std::io::Error> {
let mut file = std::fs::File::create(path)?;
let _ = file.write_all(self.buf())?;
Ok(())
}
イベントの追加
ファイルのエクスポートを実行するために入力キーの処理を追加します。
起動時に引数でファイル指定されている場合は内容を上書き、指定がない場合はエラーメッセージを出すようにしました。
ファイルのpathを指定するような機能を追加しても良かったのですが、次のバージョンの機能としたいと思います。
// ファイルへ保存
KeyCode::Char('s') | KeyCode::Char('S') => {
if let Some(path) = message.current_file().path() {
if let Err(e) = message.bin_data().export_to(path) {
message.notice_mut().add(e.to_string());
} else {
let success_msg = String::from("Saved!");
message.notice_mut().add(success_msg);
}
} else {
let err_msg = String::from("Not specified file path");
message.notice_mut().add(err_msg);
}
}
エクスポート成否のステータス表示
エクスポートの成否がわかるように画面にメッセージを出すようにしています。
一瞬だけ表示するとわからないのでしばらく表示するような仕掛けをいれるため新たにNotice構造体を追加しました。
// ステータス伝達
pub(crate) struct Notice {
count: Cell<u8>,
notice: RefCell<VecDeque<String>>,
cache: RefCell<String>,
}
impl Notice {
pub(crate) fn new() -> Self {
Self {
count: Cell::new(0),
notice: RefCell::new(VecDeque::new()),
cache: RefCell::new(String::new()),
}
}
pub(crate) fn add(&mut self, state: String) {
self.notice.borrow_mut().push_back(state);
}
pub(crate) fn pop_front(&self) -> String {
const LIMIT: u8 = 2;
match self.count.get() {
0 => {
if let Some(state) = self.notice.borrow_mut().pop_front() {
*self.cache.borrow_mut() = format!(" {state} ");
self.count.set(1);
} else {
self.cache.borrow_mut().clear();
}
}
x if x > LIMIT => {
self.count.set(0);
}
x => {
self.count.set(x + 1);
}
}
self.cache.borrow().clone()
}
}
// メッセージ取り出し
let notice = message.notice().pop_front();
let status_bar_mid = Line::from(notice).centered();
この処理で苦労したことがpop_front()
の処理です。
pop_front()
を呼ぶ場面ではmessageは可変参照を使わないことを想定していたのです。
ですが、pop_front()
はmessage構造体の中の変数の内容に変更が生じます。
これはコードのデザインが悪い可能性が高いのですが、ここからデザインに変更を加えるのは大変なので別の方法を検討しました。
Rustの標準ライブラリにはCellやRefCellのような内部可変性を扱う機能が提供されています。今回はこちらを使用しました。
unsafe寄りな機能なのでスレッドで使えない等制約がありますがいまのところ、そういった機能は使わないので一旦これで良しとします。
TOC
- 準備
- ターミナル入出力を試す
- ターミナル系crateを利用する
- 編集データと表示処理
- 画面制御
- 編集データへの入出力
- その他
GitHubにコードをアップロードしています。
コードのコメントに書かれているfirst_stepなどをcargoコマンドに渡すと実行できます。
# Example
$ cargo run --example first_step