diff --git a/television/channels/cable.rs b/television/channels/cable.rs index 4546c9d..519193b 100644 --- a/television/channels/cable.rs +++ b/television/channels/cable.rs @@ -19,8 +19,8 @@ use crate::matcher::Matcher; use crate::matcher::{config::Config, injector::Injector}; use crate::utils::command::shell_command; -#[derive(Debug, Clone)] -enum PreviewKind { +#[derive(Debug, Clone, PartialEq)] +pub enum PreviewKind { Command(PreviewCommand), Builtin(PreviewType), None, @@ -63,7 +63,7 @@ impl From for Channel { } } -fn parse_preview_kind(command: &PreviewCommand) -> Result { +pub fn parse_preview_kind(command: &PreviewCommand) -> Result { debug!("Parsing preview kind for command: {:?}", command); let re = Regex::new(r"^\:(\w+)\:$").unwrap(); if let Some(captures) = re.captures(&command.command) { diff --git a/television/channels/stdin.rs b/television/channels/stdin.rs index 581c84c..94dabef 100644 --- a/television/channels/stdin.rs +++ b/television/channels/stdin.rs @@ -18,7 +18,7 @@ pub struct Channel { } impl Channel { - pub fn new(preview_type: Option) -> Self { + pub fn new(preview_type: PreviewType) -> Self { let matcher = Matcher::new(Config::default()); let injector = matcher.injector(); @@ -26,7 +26,7 @@ impl Channel { Self { matcher, - preview_type: preview_type.unwrap_or_default(), + preview_type, selected_entries: HashSet::with_hasher(FxBuildHasher), } } @@ -34,7 +34,7 @@ impl Channel { impl Default for Channel { fn default() -> Self { - Self::new(None) + Self::new(PreviewType::default()) } } diff --git a/television/cli/mod.rs b/television/cli/mod.rs index 57ed260..cfce422 100644 --- a/television/cli/mod.rs +++ b/television/cli/mod.rs @@ -4,11 +4,11 @@ use std::path::Path; use anyhow::{anyhow, Result}; use tracing::debug; +use crate::channels::cable::{parse_preview_kind, PreviewKind}; use crate::channels::{ cable::CableChannelPrototype, entry::PreviewCommand, CliTvChannel, }; -use crate::cli::args::{Cli, Command, Shell}; -use crate::utils::shell::Shell as UtilShell; +use crate::cli::args::{Cli, Command}; use crate::{ cable, config::{get_config_dir, get_data_dir}, @@ -16,22 +16,10 @@ use crate::{ pub mod args; -impl From for UtilShell { - fn from(val: Shell) -> Self { - match val { - Shell::Bash => UtilShell::Bash, - Shell::Zsh => UtilShell::Zsh, - Shell::Fish => UtilShell::Fish, - Shell::PowerShell => UtilShell::PowerShell, - Shell::Cmd => UtilShell::Cmd, - } - } -} - #[derive(Debug, Clone)] pub struct PostProcessedCli { pub channel: ParsedCliChannel, - pub preview_command: Option, + pub preview_kind: PreviewKind, pub no_preview: bool, pub tick_rate: Option, pub frame_rate: Option, @@ -46,7 +34,7 @@ impl Default for PostProcessedCli { fn default() -> Self { Self { channel: ParsedCliChannel::Builtin(CliTvChannel::Files), - preview_command: None, + preview_kind: PreviewKind::None, no_preview: false, tick_rate: None, frame_rate: None, @@ -68,10 +56,17 @@ impl From for PostProcessedCli { .map(std::string::ToString::to_string) .collect(); - let preview_command = cli.preview.map(|preview| PreviewCommand { - command: preview, - delimiter: cli.delimiter.clone(), - }); + let preview_kind = cli + .preview + .map(|preview| PreviewCommand { + command: preview, + delimiter: cli.delimiter.clone(), + }) + .map(|preview_command| { + parse_preview_kind(&preview_command) + .expect("Error parsing preview command") + }) + .unwrap_or(PreviewKind::None); let channel: ParsedCliChannel; let working_directory: Option; @@ -98,7 +93,7 @@ impl From for PostProcessedCli { Self { channel, - preview_command, + preview_kind, no_preview: cli.no_preview, tick_rate: cli.tick_rate, frame_rate: cli.frame_rate, @@ -264,6 +259,8 @@ Data directory: {data_dir_path}" #[cfg(test)] mod tests { + use crate::channels::entry::PreviewType; + use super::*; #[test] @@ -290,8 +287,8 @@ mod tests { ParsedCliChannel::Builtin(CliTvChannel::Files) ); assert_eq!( - post_processed_cli.preview_command, - Some(PreviewCommand { + post_processed_cli.preview_kind, + PreviewKind::Command(PreviewCommand { command: "bat -n --color=always {}".to_string(), delimiter: ":".to_string() }) @@ -337,4 +334,52 @@ mod tests { ); assert_eq!(post_processed_cli.command, None); } + + #[test] + fn test_builtin_previewer_files() { + let cli = Cli { + channel: "files".to_string(), + preview: Some(":files:".to_string()), + no_preview: false, + delimiter: ":".to_string(), + tick_rate: Some(50.0), + frame_rate: Some(60.0), + passthrough_keybindings: None, + input: None, + command: None, + working_directory: None, + autocomplete_prompt: None, + }; + + let post_processed_cli: PostProcessedCli = cli.into(); + + assert_eq!( + post_processed_cli.preview_kind, + PreviewKind::Builtin(PreviewType::Files) + ); + } + + #[test] + fn test_builtin_previewer_env() { + let cli = Cli { + channel: "files".to_string(), + preview: Some(":env_var:".to_string()), + no_preview: false, + delimiter: ":".to_string(), + tick_rate: Some(50.0), + frame_rate: Some(60.0), + passthrough_keybindings: None, + input: None, + command: None, + working_directory: None, + autocomplete_prompt: None, + }; + + let post_processed_cli: PostProcessedCli = cli.into(); + + assert_eq!( + post_processed_cli.preview_kind, + PreviewKind::Builtin(PreviewType::EnvVar) + ); + } } diff --git a/television/main.rs b/television/main.rs index 2a052ac..2f29017 100644 --- a/television/main.rs +++ b/television/main.rs @@ -5,6 +5,7 @@ use std::process::exit; use anyhow::Result; use clap::Parser; +use television::channels::cable::PreviewKind; use television::utils::clipboard::CLIPBOARD; use tracing::{debug, error, info}; @@ -30,67 +31,37 @@ async fn main() -> Result<()> { television::errors::init()?; television::logging::init()?; + // post-process the CLI arguments let args: PostProcessedCli = Cli::parse().into(); debug!("{:?}", args); + // load the configuration file let mut config = Config::new(&ConfigEnv::init()?)?; - if let Some(command) = args.command { - match command { - Command::ListChannels => { - list_channels(); - exit(0); - } - Command::InitShell { shell } => { - let target_shell = Shell::from(shell); - // the completion scripts for the various shells are templated - // so that it's possible to override the keybindings triggering - // shell autocomplete and command history in tv - let script = render_autocomplete_script_template( - target_shell, - completion_script(target_shell)?, - &config.shell_integration, - )?; - println!("{script}"); - exit(0); - } - } - } + // optionally handle subcommands + args.command.as_ref().map(handle_subcommands); + // optionally change the working directory + args.working_directory.as_ref().map(set_current_dir); + + // optionally override configuration values with CLI arguments config.config.tick_rate = args.tick_rate.unwrap_or(config.config.tick_rate); if args.no_preview { config.ui.show_preview_panel = false; } - if let Some(working_directory) = &args.working_directory { - let path = Path::new(working_directory); - if !path.exists() { - error!( - "Working directory \"{}\" does not exist", - &working_directory - ); - println!( - "Error: Working directory \"{}\" does not exist", - &working_directory - ); - exit(1); - } - env::set_current_dir(path)?; - } - - CLIPBOARD.with(<_>::default); - + // determine the channel to use based on the CLI arguments and configuration let channel = determine_channel(args.clone(), &config, is_readable_stdin())?; + CLIPBOARD.with(<_>::default); + let mut app = App::new(channel, config, &args.passthrough_keybindings, args.input); - stdout().flush()?; let output = app.run(stdout().is_terminal(), false).await?; info!("{:?}", output); - // lock stdout let stdout_handle = stdout().lock(); let mut bufwriter = BufWriter::new(stdout_handle); if let Some(passthrough) = output.passthrough { @@ -105,6 +76,42 @@ async fn main() -> Result<()> { exit(0); } +pub fn set_current_dir(path: &String) -> Result<()> { + let path = Path::new(path); + if !path.exists() { + error!("Working directory \"{}\" does not exist", path.display()); + println!( + "Error: Working directory \"{}\" does not exist", + path.display() + ); + exit(1); + } + env::set_current_dir(path)?; + Ok(()) +} + +pub fn handle_subcommands(command: &Command) -> Result<()> { + match command { + Command::ListChannels => { + list_channels(); + exit(0); + } + Command::InitShell { shell } => { + let target_shell = Shell::from(shell); + // the completion scripts for the various shells are templated + // so that it's possible to override the keybindings triggering + // shell autocomplete and command history in tv + let script = render_autocomplete_script_template( + target_shell, + completion_script(target_shell)?, + &Config::default().shell_integration, + )?; + println!("{script}"); + exit(0); + } + } +} + pub fn determine_channel( args: PostProcessedCli, config: &Config, @@ -113,7 +120,13 @@ pub fn determine_channel( if readable_stdin { debug!("Using stdin channel"); Ok(TelevisionChannel::Stdin(StdinChannel::new( - args.preview_command.map(PreviewType::Command), + match &args.preview_kind { + PreviewKind::Command(ref preview_command) => { + PreviewType::Command(preview_command.clone()) + } + PreviewKind::Builtin(preview_type) => preview_type.clone(), + PreviewKind::None => PreviewType::None, + }, ))) } else if let Some(prompt) = args.autocomplete_prompt { let channel = guess_channel_from_prompt( @@ -175,7 +188,7 @@ mod tests { &args, &config, true, - &TelevisionChannel::Stdin(StdinChannel::new(None)), + &TelevisionChannel::Stdin(StdinChannel::new(PreviewType::None)), ); } diff --git a/television/utils/shell.rs b/television/utils/shell.rs index 41e46a5..0f9efc3 100644 --- a/television/utils/shell.rs +++ b/television/utils/shell.rs @@ -1,5 +1,7 @@ +use crate::cli::args::Shell as CliShell; use crate::config::shell_integration::ShellIntegrationConfig; use anyhow::Result; + #[derive(Debug, Clone, Copy, PartialEq)] pub enum Shell { Bash, @@ -9,6 +11,30 @@ pub enum Shell { Cmd, } +impl From for Shell { + fn from(val: CliShell) -> Self { + match val { + CliShell::Bash => Shell::Bash, + CliShell::Zsh => Shell::Zsh, + CliShell::Fish => Shell::Fish, + CliShell::PowerShell => Shell::PowerShell, + CliShell::Cmd => Shell::Cmd, + } + } +} + +impl From<&CliShell> for Shell { + fn from(val: &CliShell) -> Self { + match val { + CliShell::Bash => Shell::Bash, + CliShell::Zsh => Shell::Zsh, + CliShell::Fish => Shell::Fish, + CliShell::PowerShell => Shell::PowerShell, + CliShell::Cmd => Shell::Cmd, + } + } +} + const COMPLETION_ZSH: &str = include_str!("shell/completion.zsh"); const COMPLETION_BASH: &str = include_str!("shell/completion.bash"); const COMPLETION_FISH: &str = include_str!("shell/completion.fish");