diff --git a/Cargo.lock b/Cargo.lock index 9567e9c..1d32918 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -226,8 +226,10 @@ name = "live-cli" version = "0.1.0" dependencies = [ "ansi4tui", + "anyhow", "atty", "crossterm", + "shellwords", "tui", ] @@ -539,6 +541,16 @@ dependencies = [ "opaque-debug", ] +[[package]] +name = "shellwords" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89e515aa4699a88148ed5ef96413ceef0048ce95b43fbc955a33bde0a70fcae6" +dependencies = [ + "lazy_static", + "regex", +] + [[package]] name = "signal-hook" version = "0.1.17" diff --git a/Cargo.toml b/Cargo.toml index a5ee92b..991799c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,3 +10,5 @@ tui = "0.19" crossterm = "0.25" atty = "0.2.14" ansi4tui = {path = "./ansi4tui/"} +anyhow = "1.0.66" +shellwords = "1.1.0" diff --git a/src/command.rs b/src/command.rs index bdb53dd..fc42adb 100644 --- a/src/command.rs +++ b/src/command.rs @@ -24,9 +24,22 @@ impl Default for CommandResult<'_> { } pub fn run(app: &mut App) { - let mut child = Command::new("jq") - .arg("-C") - .arg(&app.cmdline) + let args = match shellwords::split(&app.cmdline) { + Ok(a) => a, + Err(e) => { + app.command_result = CommandResult { + status_success: false, + stdout: Paragraph::new(""), + stderr: Paragraph::new(format!("Argument error: {e:?}")), + }; + return; + } + }; + + let mut child = Command::new(&app.hidden_command) + .args(&app.hidden_options) + // .arg(&app.cmdline) + .args(args) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) diff --git a/src/main.rs b/src/main.rs index 9c4cade..ada8010 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,8 @@ #![allow(clippy::module_name_repetitions, clippy::cast_possible_truncation)] mod command; +use anyhow::bail; +use anyhow::Result; use command::CommandResult; use std::{ io::{self, Write}, @@ -27,6 +29,12 @@ use crossterm::{ /// The state of the application pub struct App<'a> { + /// The actual command to be called + hidden_command: String, + + // A list of hidden options passed to the command + hidden_options: Vec<&'static str>, + /// The line the user inputs cmdline: String, @@ -38,28 +46,103 @@ pub struct App<'a> { /// Should every keystroke transform the original text? autorun: bool, + + /// Should this command be run when the program starts + run_initial: bool, } impl App<'_> { #[must_use] - pub fn new_with_input(input: String) -> Self { - Self { - cmdline: String::new(), - text_orig: Arc::new(input), - command_result: CommandResult::default(), - autorun: true, + pub fn from_template(input: String, template: &Template) -> Self { + let text_orig = Arc::new(input); + let command_result = CommandResult::default(); + let hidden_command = template.command(); + + match template { + Template::Generic(_) => Self { + cmdline: String::new(), + text_orig, + command_result: CommandResult::default(), + autorun: true, + hidden_command, + hidden_options: vec![], + run_initial: true, + }, + Template::Jq => Self { + cmdline: String::from("."), + text_orig, + command_result, + autorun: true, + hidden_command, + hidden_options: vec!["-C"], + run_initial: true, + }, + Template::Grep | Template::Rg => Self { + cmdline: String::new(), + text_orig, + command_result: CommandResult::default(), + autorun: true, + hidden_command, + hidden_options: vec!["--color=always"], + run_initial: false, + }, + Template::Sed | Template::Awk => Self { + cmdline: String::new(), + text_orig, + command_result: CommandResult::default(), + autorun: true, + hidden_command, + hidden_options: vec![], + run_initial: false, + }, } } } -fn main() -> Result<(), io::Error> { - if atty::is(atty::Stream::Stdin) { - return Err(io::Error::new( - io::ErrorKind::Other, - "You must send stdin to this command", - )); +pub enum Template { + Generic(String), + Jq, + Grep, + Rg, + Sed, + Awk, +} + +impl Template { + pub fn from(s: String) -> Self { + match s.to_lowercase().trim() { + "jq" => Self::Jq, + "grep" => Self::Grep, + "rg" => Self::Rg, + "sed" => Self::Sed, + c => Self::Generic(c.to_string()), + } } + pub fn command(&self) -> String { + match self { + Template::Generic(c) => c.to_string(), + Template::Jq => String::from("jq"), + Template::Grep => String::from("grep"), + Template::Rg => String::from("rg"), + Template::Sed => String::from("sed"), + Template::Awk => String::from("awk"), + } + } +} + +fn main() -> Result<()> { + // Error if we aren't getting any stdin + if atty::is(atty::Stream::Stdin) { + bail!("You must send stdin to this command"); + } + + // Get the arguments + let arg = match std::env::args().nth(1) { + Some(a) => a, + None => bail!("You must pass a command"), + }; + enable_raw_mode()?; let mut stdout = io::stdout(); // execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?; @@ -71,7 +154,7 @@ fn main() -> Result<(), io::Error> { let text_orig = io::read_to_string(io::stdin())?; // Run the actual application - let app = App::new_with_input(text_orig); + let app = App::from_template(text_orig, &Template::from(arg)); let _res = run_app(&mut terminal, app); // Restore terminal @@ -87,6 +170,10 @@ fn main() -> Result<(), io::Error> { } fn run_app(terminal: &mut Terminal, mut app: App) -> io::Result> { + if app.run_initial { + command::run(&mut app); + } + loop { terminal.draw(|f| ui(f, &app))?; @@ -119,7 +206,6 @@ fn run_app(terminal: &mut Terminal, mut app: App) -> io::Result(f: &mut Frame, app: &App) { let vertical_chunks = Layout::default() .direction(Direction::Vertical) - .margin(2) .constraints([Constraint::Min(3), Constraint::Length(3)].as_ref()) .split(f.size());