use shlex::split; use std::env; use std::ffi::OsString; use std::io; use std::io::Write; use std::process::{Command, Stdio}; pub mod date { use chrono::naive::NaiveDate; use chrono::Local; #[derive(PartialEq, Debug, Clone, Copy)] pub struct Date { date: NaiveDate, } impl Date { pub fn new(year: i32, month: u32, day: u32) -> Self { Self { date: NaiveDate::from_ymd_opt(year, month, day).unwrap(), } } pub fn try_new(year: i32, month: u32, day: u32) -> Result { match NaiveDate::from_ymd_opt(year, month, day) { Some(d) => Ok(Self { date: d }), None => Err("error: failed to parse the date"), } } pub fn now() -> Self { Self { date: Local::now().naive_local().date(), } } pub fn cmp(&self, other: &Self) -> core::cmp::Ordering { self.date.cmp(&other.date) } } impl std::fmt::Display for Date { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.date.to_string()) } } } #[cfg(target_arch = "wasm32")] pub mod rnd { use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { #[wasm_bindgen(js_name = hel_rnd_range)] fn extern_rnd_range(start: u32, end: u32) -> u32; } pub fn range(start: u32, end: u32) -> u32 { extern_rnd_range(start, end) } } #[cfg(not(target_arch = "wasm32"))] pub mod rnd { use rand::{thread_rng, Rng}; pub fn range(start: u32, end: u32) -> u32 { thread_rng().gen_range(start..end) } } #[cfg(target_arch = "wasm32")] pub mod home { pub fn dir() -> std::path::PathBuf { std::path::PathBuf::new() } } #[cfg(unix)] pub mod home { use home::home_dir; use std::path::PathBuf; pub fn dir() -> PathBuf { home_dir().unwrap() } } #[cfg(unix)] pub mod editor { use crate::structs::LKErr; use rustyline::error::ReadlineError; use rustyline::config::Configurer; use std::sync::Arc; use parking_lot::Mutex; pub type EditorRef = Arc>; #[derive(Debug)] pub struct Editor { editor: rustyline::Editor<()>, } impl Editor { pub fn new() -> EditorRef { let mut editor = rustyline::Editor::<()>::new().unwrap(); editor.set_max_history_size(10000); Arc::new(Mutex::new(Self { editor: editor, })) } pub fn clear_history(&mut self) { self.editor.clear_history(); } pub fn add_history_entry(&mut self, entry: &str) { self.editor.add_history_entry(entry); } pub fn load_history<'a>(&mut self, fname: &str) -> Result<(), LKErr<'a>> { match self.editor.load_history(&fname) { Ok(_) => Ok(()), Err(_) => Err(LKErr::Error("failed to read history file")), } } pub fn save_history<'a>(&mut self, fname: &str) -> Result<(), LKErr<'a>> { match self.editor.save_history(&fname) { Ok(_) => Ok(()), Err(ReadlineError::Eof | ReadlineError::Interrupted) => Err(LKErr::EOF), Err(_) => Err(LKErr::Error("failed to write history file")), } } pub fn readline<'a>(&mut self, prompt: &str) -> Result> { match self.editor.readline(&prompt) { Ok(line) => Ok(line), Err(_) => Err(LKErr::Error("failed to read from input")), } } } pub fn password(pwname: String) -> std::io::Result { rpassword::prompt_password(format!("Password for {}: ", pwname)) } } #[cfg(target_arch = "wasm32")] pub mod editor { use crate::structs::LKErr; use parking_lot::Mutex; use std::sync::Arc; use wasm_bindgen::prelude::*; // Mirror the unix editor's contract so repl.rs (which holds an `EditorRef` // and calls `.lock()`) compiles unchanged under wasm. pub type EditorRef = Arc>; #[wasm_bindgen] extern "C" { // Synchronous: the host page returns the current master-password value. // (The old read/poll pair used thread::sleep, which deadlocks the single // browser thread — never use blocking polling under wasm.) #[wasm_bindgen(js_name = hel_get_password)] fn extern_get_password(prompt: &str) -> String; } #[derive(Debug)] pub struct Editor { #[allow(dead_code)] history: Vec, } impl Editor { pub fn new() -> EditorRef { Arc::new(Mutex::new(Self { history: vec![] })) } pub fn clear_history(&mut self) { self.history.clear(); } pub fn add_history_entry(&mut self, entry: &str) { self.history.push(entry.to_string()); } pub fn load_history<'a>(&mut self, _fname: &str) -> Result<(), LKErr<'a>> { Ok(()) } pub fn save_history<'a>(&mut self, _fname: &str) -> Result<(), LKErr<'a>> { Ok(()) } pub fn readline<'a>(&mut self, _prompt: &str) -> Result> { Ok("".to_string()) } } pub fn password(prompt: String) -> std::io::Result { Ok(extern_get_password(&prompt)) } } pub fn call_cmd_with_input(cmd: &str, args: &Vec, input: &str) -> io::Result { let mut cmd = Command::new(cmd) .args(args) // Export runtime `set …` config (uppercased) into the child, so e.g. // `set hel_notion_token …` reaches a spawned `hel store`/`hel load`. .envs(crate::structs::config_envs()) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .spawn()?; { let stdin = cmd.stdin.as_mut().unwrap(); stdin .write_all(input.as_bytes()) .expect("Failed to write input to stdin"); } let output = cmd.wait_with_output()?; match String::from_utf8(output.stdout) { Ok(x) => Ok(x), Err(err) => Err(io::Error::new(io::ErrorKind::InvalidData, err.utf8_error())), } } pub fn get_cmd_args_from_command(command: &str) -> io::Result<(String, Vec)> { let args = match split(command) { Some(c) => c, None => { return Err(io::Error::new( io::ErrorKind::InvalidData, format!("Failed to parse the command: {:?}", command), )) } }; Ok((shellexpand::full(&args[0]).unwrap().into_owned(), args[1..].to_vec())) } pub fn get_copy_command_from_env() -> (String, Vec) { let cmd_os_str = env::var_os("HEL_PB").unwrap_or_else(|| match env::consts::OS { _ if env::var("TMUX").is_ok() => OsString::from("tmux load-buffer -"), "macos" => OsString::from("pbcopy"), "linux" => OsString::from("xclip"), _ => OsString::from("cat"), }); get_cmd_args_from_command(&cmd_os_str.to_string_lossy()).unwrap_or_else(|_| ("cat".to_string(), vec![])) } #[cfg(test)] mod tests { use super::*; #[test] fn cmd_exec_test() { assert_eq!(call_cmd_with_input("true", &vec![], "").unwrap(), "".to_string()); assert_eq!(call_cmd_with_input("cat", &vec![], "ok").unwrap(), "ok".to_string()); assert_eq!( call_cmd_with_input( "cat", &vec![], r###"line 1 line 2 line 3 line 4"### ) .unwrap(), "line 1\nline 2\nline 3\nline 4".to_string() ); assert_ne!(call_cmd_with_input("cat", &vec![], "notok").unwrap(), "ok".to_string()); assert_eq!( call_cmd_with_input("echo", &vec!["-n".to_string(), "test is ok".to_string()], "").unwrap(), "test is ok".to_string() ); } #[test] fn check_correct_stdin() { let cmd = "cat"; let args = vec![]; let input = "Hello World!"; let output = call_cmd_with_input(cmd, &args, input).unwrap(); assert_eq!(output, "Hello World!"); } }