From 9b75cb6144cf50ed9750a8bcf4689cb218df375b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kha=C3=AFs=20COLIN?= Date: Sat, 10 May 2025 10:20:26 +0200 Subject: [PATCH] feat(cli): readline history --- notes.org | 12 ++++++------ src/cli.rs | 45 +++++++++++++++++++++++++++++++++++++++++++-- src/main.rs | 10 +++++++++- 3 files changed, 58 insertions(+), 9 deletions(-) diff --git a/notes.org b/notes.org index 6db9d88..3286ee1 100644 --- a/notes.org +++ b/notes.org @@ -189,24 +189,24 @@ CLOCK: [2025-05-04 dim. 14:01]--[2025-05-04 dim. 14:14] => 0:13 * DONE remove old FromStr parser implementation -* TODO use a better readline impl +* DONE use a better readline impl ** DONE inform myself on the different alternatives and decide on one i will use rustyline, since it seems like the most feature-complete ** DONE do the impl -** TODO make history work +** DONE make history work *** DONE have the rl instance be spawned from main -*** TODO figure out how to locate the app data directory on linux +*** DONE figure out how to locate the app data directory on linux -*** TODO create our own app data directory +*** DONE create our own app data directory -*** TODO load and save the history from a file in this directory +*** DONE load and save the history from a file in this directory -* TODO handle non-interactive input better +* DONE handle non-interactive input better * TODO cli tests using insta-cmd https://insta.rs/docs/cmd/ diff --git a/src/cli.rs b/src/cli.rs index e44b26f..4aa33d0 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,5 +1,46 @@ -use rustyline::{Editor, history::FileHistory}; +use std::path::PathBuf; + +use rustyline::{history::FileHistory, Editor}; + +fn xdg_state_dir() -> Option { + if let Ok(dir) = std::env::var("XDG_STATE_DIR") { + Some(PathBuf::from(dir)) + } else if let Ok(home) = std::env::var("HOME") { + if home.is_empty() { + None + } else { + Some(PathBuf::from(home).join(".local/state")) + } + } else { + None + } +} + +fn state_dir() -> Option { + if let Some(dir) = xdg_state_dir() { + let dir = dir.join("osdb"); + std::fs::create_dir_all(&dir).ok()?; + Some(dir) + } else { + None + } +} + +pub fn history_file() -> Option { + if let Some(state) = state_dir() { + Some(state.join("cli_history")) + } else { + eprintln!("Warning: failed to find or create XDG_STATE_DIR for osdb."); + eprintln!("Warning: either set XDG_STATE_DIR or HOME, and ensure osdb has write permissions to that directory."); + None + } +} pub fn read_input(rl: &mut Editor<(), FileHistory>) -> Option { - rl.readline("osdb> ").ok() + if let Ok(result) = rl.readline("osdb> ") { + let _ = rl.add_history_entry(&result); + Some(result) + } else { + None + } } diff --git a/src/main.rs b/src/main.rs index 6691c8f..9085a97 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,14 @@ use osdb::branding::startup_msg; -use osdb::cli::read_input; +use osdb::cli::{history_file, read_input}; use osdb::error_display::OSDBError as _; use osdb::parser::parse; fn main() { let mut rl = rustyline::DefaultEditor::new().expect("failed to create stdin reader"); + let history_file = history_file(); + if let Some(history_file) = &history_file { + let _ = rl.load_history(history_file); + } println!("{}", startup_msg()); @@ -29,5 +33,9 @@ fn main() { } } + if let Some(history_file) = &history_file { + let _ = rl.save_history(history_file); + } + println!("Good-bye"); }