From 9347bc39720c04c05327ed30df13f22b4ddf525e Mon Sep 17 00:00:00 2001 From: Oleksandr Kozachuk Date: Sat, 17 Dec 2022 14:54:16 +0100 Subject: [PATCH] Implement possibility to copy output of commands to paste buffers. --- Cargo.toml | 1 + src/parser.rs | 2 ++ src/repl.rs | 21 +++++++++++++++++++++ src/structs.rs | 1 + src/utils.rs | 28 +++++++++++++++++++++++----- 5 files changed, 48 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index cd02c0a..f8fce19 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,3 +19,4 @@ home = "0.5.4" sha1 = "0.10.5" base64 = "0.20.0" rpassword = "7.2.0" +shlex = "1.1.0" diff --git a/src/parser.rs b/src/parser.rs index 48efcf8..e540846 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -16,6 +16,7 @@ peg::parser! { / ls_cmd() / mv_cmd() / rm_cmd() + / pb_cmd() / enc_cmd() / pass_cmd() / noop_cmd() @@ -75,6 +76,7 @@ peg::parser! { rule noop_cmd() -> Command<'input> = (" " / "\r" / "\n" / "\t")* ("#" comment())? { Command::Noop } rule help_cmd() -> Command<'input> = "help" { Command::Help } rule quit_cmd() -> Command<'input> = "quit" { Command::Quit } + rule pb_cmd() -> Command<'input> = "pb" _ e:$(([' '..='~'])+) { Command::PasteBuffer(e.to_string()) } rule ls_cmd() -> Command<'input> = "ls" f:comment()? { Command::Ls(f.unwrap_or(".".to_string())) } rule add_cmd() -> Command<'input> = "add" _ name:name() { Command::Add(Rc::new(RefCell::new(name))) } rule error_cmd() -> Command<'input> = "error" _ e:$(([' '..='~'])+) { Command::Error(LKErr::Error(e)) } diff --git a/src/repl.rs b/src/repl.rs index 059e0a1..12eba52 100644 --- a/src/repl.rs +++ b/src/repl.rs @@ -8,6 +8,7 @@ use crate::lk::LK; use crate::parser::command_parser; use crate::password::{fix_password_recursion, PasswordRef}; use crate::structs::{Command, LKErr, Radix, HISTORY_FILE}; +use crate::utils::{ call_cmd_with_input, get_copy_command_from_env }; #[derive(Debug)] pub struct LKRead { @@ -183,6 +184,25 @@ impl<'a> LKEval<'a> { out.push(pass); } + fn cmd_pb(&self, out: &mut Vec, command: &String) { + match command_parser::cmd(command) { + Ok(cmd) => { + let print = LKEval::new(cmd, self.state.clone(), prompt_password).eval(); + let data = print.out.join("\n"); + let (copy_command, copy_cmd_args) = get_copy_command_from_env(); + match call_cmd_with_input(©_command, ©_cmd_args, &data) { + Ok(s) if s.len() > 0 => { + out.push(format!("Copied output with the command {}, and got following output:", copy_command)); + out.push(s.trim().to_string()); + } + Ok(_) => out.push(format!("Copied output with command {}", copy_command)), + Err(e) => out.push(format!("error: failed to copy: {}", e.to_string())), + }; + } + Err(e) => out.push(format!("error: faild to parse command {}: {}", command, e.to_string())), + }; + } + fn cmd_ls(&self, out: &mut Vec, filter: String) { let re = match Regex::new(&filter) { Ok(re) => re, @@ -247,6 +267,7 @@ impl<'a> LKEval<'a> { None => out.push("error: password not found".to_string()), }, Command::Enc(name) => self.cmd_enc(&mut out, name), + Command::PasteBuffer(command) => self.cmd_pb(&mut out, command), Command::Pass(name) => match self.get_password(name) { Some(p) => { self.state.borrow_mut().secrets.insert( diff --git a/src/structs.rs b/src/structs.rs index 022e9a2..e606242 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -26,6 +26,7 @@ pub enum Command<'a> { Rm(Name), Enc(Name), Pass(Name), + PasteBuffer(String), Comment(Name, Comment), Error(LKErr<'a>), Noop, diff --git a/src/utils.rs b/src/utils.rs index 2ecc690..4020c8c 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,8 +1,11 @@ +use std::env; use std::io; use std::io::{Read, Write}; use std::process::{Command, Stdio}; +use shlex::split; +use std::ffi::OsString; -pub fn call_cmd_with_input(cmd: &str, args: Vec<&str>, input: &str) -> io::Result { +pub fn call_cmd_with_input(cmd: &str, args: &Vec, input: &str) -> io::Result { let mut cmd = Command::new(cmd).args(args).stdin(Stdio::piped()).stdout(Stdio::piped()).spawn()?; let mut stdin = cmd.stdin.take().unwrap(); let stdout = cmd.stdout.as_mut().unwrap(); @@ -20,15 +23,30 @@ pub fn call_cmd_with_input(cmd: &str, args: Vec<&str>, input: &str) -> io::Resul } } +pub fn get_copy_command_from_env() -> (String, Vec) { + let cmd_os_str = env::var_os("LESSKEY_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"), + }); + let args = split(&cmd_os_str.to_string_lossy()).unwrap_or_else(|| vec!["cat".to_string()]); + (args[0].clone(), args[1..].to_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_ne!(call_cmd_with_input("cat", vec![], "notok").unwrap(), "ok".to_string()); - assert_eq!(call_cmd_with_input("echo", vec!["-n", "test is ok"], "").unwrap(), "test is ok".to_string()); + 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()); } }