From 3b7fb0c6d6e73a6558a99648c5269ae458ab9404 Mon Sep 17 00:00:00 2001 From: Alex Pasmantier <47638216+alexpasmantier@users.noreply.github.com> Date: Thu, 9 Jan 2025 13:30:31 +0100 Subject: [PATCH] refactor(cable): stream in cable results + better error logging + default delimiter consistency (#257) --- .../television-channels/src/channels/cable.rs | 38 ++++++++++++++----- .../src/previewers/command.rs | 20 ++++++---- crates/television/cli.rs | 4 +- 3 files changed, 43 insertions(+), 19 deletions(-) diff --git a/crates/television-channels/src/channels/cable.rs b/crates/television-channels/src/channels/cable.rs index 4743eae..2f0f36f 100644 --- a/crates/television-channels/src/channels/cable.rs +++ b/crates/television-channels/src/channels/cable.rs @@ -1,4 +1,6 @@ use std::collections::HashSet; +use std::io::{BufRead, BufReader}; +use std::process::Stdio; use color_eyre::Result; use lazy_static::lazy_static; @@ -104,19 +106,37 @@ impl Channel { #[allow(clippy::unused_async)] async fn load_candidates(command: String, injector: Injector) { debug!("Loading candidates from command: {:?}", command); - let output = shell_command() + let mut child = shell_command() .arg(command) - .output() + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() .expect("failed to execute process"); - let decoded_output = String::from_utf8_lossy(&output.stdout); - debug!("Decoded output: {:?}", decoded_output); + if let Some(out) = child.stdout.take() { + let reader = BufReader::new(out); + let mut produced_output = false; - for line in decoded_output.lines() { - if !line.trim().is_empty() { - let () = injector.push(line.to_string(), |e, cols| { - cols[0] = e.clone().into(); - }); + for line in reader.lines() { + let line = line.unwrap(); + if !line.trim().is_empty() { + let () = injector.push(line, |e, cols| { + cols[0] = e.clone().into(); + }); + produced_output = true; + } + } + + if !produced_output { + let reader = BufReader::new(child.stderr.take().unwrap()); + for line in reader.lines() { + let line = line.unwrap(); + if !line.trim().is_empty() { + let () = injector.push(line, |e, cols| { + cols[0] = e.clone().into(); + }); + } + } } } } diff --git a/crates/television-previewers/src/previewers/command.rs b/crates/television-previewers/src/previewers/command.rs index 1755586..6db4197 100644 --- a/crates/television-previewers/src/previewers/command.rs +++ b/crates/television-previewers/src/previewers/command.rs @@ -1,4 +1,3 @@ -/// git log --oneline --date=short --pretty="format:%C(auto)%h %s %Cblue%an %C(green)%cd" "$@" | ~/code/rust/television/target/release/tv --preview 'git show -p --stat --pretty=fuller --color=always {0}' --delimiter ' ' use crate::previewers::cache::PreviewCache; use crate::previewers::{Preview, PreviewContent}; use lazy_static::lazy_static; @@ -27,7 +26,7 @@ pub struct CommandPreviewerConfig { delimiter: String, } -const DEFAULT_DELIMITER: &str = ":"; +const DEFAULT_DELIMITER: &str = " "; impl Default for CommandPreviewerConfig { fn default() -> Self { @@ -124,19 +123,21 @@ lazy_static! { /// let entry = Entry::new("a:given:entry:to:preview".to_string(), PreviewType::Command(command.clone())); /// let formatted_command = format_command(&command, &entry); /// -/// assert_eq!(formatted_command, "something a:given:entry:to:preview entry a"); +/// assert_eq!(formatted_command, "something 'a:given:entry:to:preview' 'entry' 'a'"); /// ``` pub fn format_command(command: &PreviewCommand, entry: &Entry) -> String { let parts = entry.name.split(&command.delimiter).collect::>(); debug!("Parts: {:?}", parts); - let mut formatted_command = command.command.replace("{}", &entry.name); + let mut formatted_command = command + .command + .replace("{}", format!("'{}'", entry.name).as_str()); formatted_command = COMMAND_PLACEHOLDER_REGEX .replace_all(&formatted_command, |caps: ®ex::Captures| { let index = caps.get(1).unwrap().as_str().parse::().unwrap(); - parts[index].to_string() + format!("'{}'", parts[index]) }) .to_string(); @@ -202,7 +203,10 @@ mod tests { ); let formatted_command = format_command(&command, &entry); - assert_eq!(formatted_command, "something an:entry:to:preview to an"); + assert_eq!( + formatted_command, + "something 'an:entry:to:preview' 'to' 'an'" + ); } #[test] @@ -232,7 +236,7 @@ mod tests { ); let formatted_command = format_command(&command, &entry); - assert_eq!(formatted_command, "something an:entry:to:preview"); + assert_eq!(formatted_command, "something 'an:entry:to:preview'"); } #[test] @@ -247,6 +251,6 @@ mod tests { ); let formatted_command = format_command(&command, &entry); - assert_eq!(formatted_command, "something an -t to"); + assert_eq!(formatted_command, "something 'an' -t 'to'"); } } diff --git a/crates/television/cli.rs b/crates/television/cli.rs index 186d1c8..eac206e 100644 --- a/crates/television/cli.rs +++ b/crates/television/cli.rs @@ -31,7 +31,7 @@ pub struct Cli { pub no_preview: bool, /// The delimiter used to extract fields from the entry to provide to the preview command - /// (defaults to ":") + /// (defaults to " ") #[arg(long, value_name = "STRING", default_value = " ", value_parser = delimiter_parser)] pub delimiter: String, @@ -283,7 +283,7 @@ pub fn guess_channel_from_prompt( #[allow(clippy::unnecessary_wraps)] fn delimiter_parser(s: &str) -> Result { Ok(match s { - "" => ":".to_string(), + "" => " ".to_string(), "\\t" => "\t".to_string(), _ => s.to_string(), })