mirror of
https://github.com/alexpasmantier/television.git
synced 2025-06-08 12:35:26 +00:00
feat(cli): allow passing builtin previewers through the cli (e.g. --preview ':files:'
) (#403)
fixes #392
This commit is contained in:
parent
1e4c34fecd
commit
47ea5a2b68
@ -19,8 +19,8 @@ use crate::matcher::Matcher;
|
|||||||
use crate::matcher::{config::Config, injector::Injector};
|
use crate::matcher::{config::Config, injector::Injector};
|
||||||
use crate::utils::command::shell_command;
|
use crate::utils::command::shell_command;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
enum PreviewKind {
|
pub enum PreviewKind {
|
||||||
Command(PreviewCommand),
|
Command(PreviewCommand),
|
||||||
Builtin(PreviewType),
|
Builtin(PreviewType),
|
||||||
None,
|
None,
|
||||||
@ -63,7 +63,7 @@ impl From<CableChannelPrototype> for Channel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_preview_kind(command: &PreviewCommand) -> Result<PreviewKind> {
|
pub fn parse_preview_kind(command: &PreviewCommand) -> Result<PreviewKind> {
|
||||||
debug!("Parsing preview kind for command: {:?}", command);
|
debug!("Parsing preview kind for command: {:?}", command);
|
||||||
let re = Regex::new(r"^\:(\w+)\:$").unwrap();
|
let re = Regex::new(r"^\:(\w+)\:$").unwrap();
|
||||||
if let Some(captures) = re.captures(&command.command) {
|
if let Some(captures) = re.captures(&command.command) {
|
||||||
|
@ -18,7 +18,7 @@ pub struct Channel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Channel {
|
impl Channel {
|
||||||
pub fn new(preview_type: Option<PreviewType>) -> Self {
|
pub fn new(preview_type: PreviewType) -> Self {
|
||||||
let matcher = Matcher::new(Config::default());
|
let matcher = Matcher::new(Config::default());
|
||||||
let injector = matcher.injector();
|
let injector = matcher.injector();
|
||||||
|
|
||||||
@ -26,7 +26,7 @@ impl Channel {
|
|||||||
|
|
||||||
Self {
|
Self {
|
||||||
matcher,
|
matcher,
|
||||||
preview_type: preview_type.unwrap_or_default(),
|
preview_type,
|
||||||
selected_entries: HashSet::with_hasher(FxBuildHasher),
|
selected_entries: HashSet::with_hasher(FxBuildHasher),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -34,7 +34,7 @@ impl Channel {
|
|||||||
|
|
||||||
impl Default for Channel {
|
impl Default for Channel {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new(None)
|
Self::new(PreviewType::default())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,11 +4,11 @@ use std::path::Path;
|
|||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
|
use crate::channels::cable::{parse_preview_kind, PreviewKind};
|
||||||
use crate::channels::{
|
use crate::channels::{
|
||||||
cable::CableChannelPrototype, entry::PreviewCommand, CliTvChannel,
|
cable::CableChannelPrototype, entry::PreviewCommand, CliTvChannel,
|
||||||
};
|
};
|
||||||
use crate::cli::args::{Cli, Command, Shell};
|
use crate::cli::args::{Cli, Command};
|
||||||
use crate::utils::shell::Shell as UtilShell;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
cable,
|
cable,
|
||||||
config::{get_config_dir, get_data_dir},
|
config::{get_config_dir, get_data_dir},
|
||||||
@ -16,22 +16,10 @@ use crate::{
|
|||||||
|
|
||||||
pub mod args;
|
pub mod args;
|
||||||
|
|
||||||
impl From<Shell> 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)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct PostProcessedCli {
|
pub struct PostProcessedCli {
|
||||||
pub channel: ParsedCliChannel,
|
pub channel: ParsedCliChannel,
|
||||||
pub preview_command: Option<PreviewCommand>,
|
pub preview_kind: PreviewKind,
|
||||||
pub no_preview: bool,
|
pub no_preview: bool,
|
||||||
pub tick_rate: Option<f64>,
|
pub tick_rate: Option<f64>,
|
||||||
pub frame_rate: Option<f64>,
|
pub frame_rate: Option<f64>,
|
||||||
@ -46,7 +34,7 @@ impl Default for PostProcessedCli {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
channel: ParsedCliChannel::Builtin(CliTvChannel::Files),
|
channel: ParsedCliChannel::Builtin(CliTvChannel::Files),
|
||||||
preview_command: None,
|
preview_kind: PreviewKind::None,
|
||||||
no_preview: false,
|
no_preview: false,
|
||||||
tick_rate: None,
|
tick_rate: None,
|
||||||
frame_rate: None,
|
frame_rate: None,
|
||||||
@ -68,10 +56,17 @@ impl From<Cli> for PostProcessedCli {
|
|||||||
.map(std::string::ToString::to_string)
|
.map(std::string::ToString::to_string)
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let preview_command = cli.preview.map(|preview| PreviewCommand {
|
let preview_kind = cli
|
||||||
|
.preview
|
||||||
|
.map(|preview| PreviewCommand {
|
||||||
command: preview,
|
command: preview,
|
||||||
delimiter: cli.delimiter.clone(),
|
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 channel: ParsedCliChannel;
|
||||||
let working_directory: Option<String>;
|
let working_directory: Option<String>;
|
||||||
@ -98,7 +93,7 @@ impl From<Cli> for PostProcessedCli {
|
|||||||
|
|
||||||
Self {
|
Self {
|
||||||
channel,
|
channel,
|
||||||
preview_command,
|
preview_kind,
|
||||||
no_preview: cli.no_preview,
|
no_preview: cli.no_preview,
|
||||||
tick_rate: cli.tick_rate,
|
tick_rate: cli.tick_rate,
|
||||||
frame_rate: cli.frame_rate,
|
frame_rate: cli.frame_rate,
|
||||||
@ -264,6 +259,8 @@ Data directory: {data_dir_path}"
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use crate::channels::entry::PreviewType;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -290,8 +287,8 @@ mod tests {
|
|||||||
ParsedCliChannel::Builtin(CliTvChannel::Files)
|
ParsedCliChannel::Builtin(CliTvChannel::Files)
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
post_processed_cli.preview_command,
|
post_processed_cli.preview_kind,
|
||||||
Some(PreviewCommand {
|
PreviewKind::Command(PreviewCommand {
|
||||||
command: "bat -n --color=always {}".to_string(),
|
command: "bat -n --color=always {}".to_string(),
|
||||||
delimiter: ":".to_string()
|
delimiter: ":".to_string()
|
||||||
})
|
})
|
||||||
@ -337,4 +334,52 @@ mod tests {
|
|||||||
);
|
);
|
||||||
assert_eq!(post_processed_cli.command, None);
|
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)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ use std::process::exit;
|
|||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use television::channels::cable::PreviewKind;
|
||||||
use television::utils::clipboard::CLIPBOARD;
|
use television::utils::clipboard::CLIPBOARD;
|
||||||
use tracing::{debug, error, info};
|
use tracing::{debug, error, info};
|
||||||
|
|
||||||
@ -30,67 +31,37 @@ async fn main() -> Result<()> {
|
|||||||
television::errors::init()?;
|
television::errors::init()?;
|
||||||
television::logging::init()?;
|
television::logging::init()?;
|
||||||
|
|
||||||
|
// post-process the CLI arguments
|
||||||
let args: PostProcessedCli = Cli::parse().into();
|
let args: PostProcessedCli = Cli::parse().into();
|
||||||
debug!("{:?}", args);
|
debug!("{:?}", args);
|
||||||
|
|
||||||
|
// load the configuration file
|
||||||
let mut config = Config::new(&ConfigEnv::init()?)?;
|
let mut config = Config::new(&ConfigEnv::init()?)?;
|
||||||
|
|
||||||
if let Some(command) = args.command {
|
// optionally handle subcommands
|
||||||
match command {
|
args.command.as_ref().map(handle_subcommands);
|
||||||
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 change the working directory
|
||||||
|
args.working_directory.as_ref().map(set_current_dir);
|
||||||
|
|
||||||
|
// optionally override configuration values with CLI arguments
|
||||||
config.config.tick_rate =
|
config.config.tick_rate =
|
||||||
args.tick_rate.unwrap_or(config.config.tick_rate);
|
args.tick_rate.unwrap_or(config.config.tick_rate);
|
||||||
if args.no_preview {
|
if args.no_preview {
|
||||||
config.ui.show_preview_panel = false;
|
config.ui.show_preview_panel = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(working_directory) = &args.working_directory {
|
// determine the channel to use based on the CLI arguments and configuration
|
||||||
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);
|
|
||||||
|
|
||||||
let channel =
|
let channel =
|
||||||
determine_channel(args.clone(), &config, is_readable_stdin())?;
|
determine_channel(args.clone(), &config, is_readable_stdin())?;
|
||||||
|
|
||||||
|
CLIPBOARD.with(<_>::default);
|
||||||
|
|
||||||
let mut app =
|
let mut app =
|
||||||
App::new(channel, config, &args.passthrough_keybindings, args.input);
|
App::new(channel, config, &args.passthrough_keybindings, args.input);
|
||||||
|
|
||||||
stdout().flush()?;
|
stdout().flush()?;
|
||||||
let output = app.run(stdout().is_terminal(), false).await?;
|
let output = app.run(stdout().is_terminal(), false).await?;
|
||||||
info!("{:?}", output);
|
info!("{:?}", output);
|
||||||
// lock stdout
|
|
||||||
let stdout_handle = stdout().lock();
|
let stdout_handle = stdout().lock();
|
||||||
let mut bufwriter = BufWriter::new(stdout_handle);
|
let mut bufwriter = BufWriter::new(stdout_handle);
|
||||||
if let Some(passthrough) = output.passthrough {
|
if let Some(passthrough) = output.passthrough {
|
||||||
@ -105,6 +76,42 @@ async fn main() -> Result<()> {
|
|||||||
exit(0);
|
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(
|
pub fn determine_channel(
|
||||||
args: PostProcessedCli,
|
args: PostProcessedCli,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
@ -113,7 +120,13 @@ pub fn determine_channel(
|
|||||||
if readable_stdin {
|
if readable_stdin {
|
||||||
debug!("Using stdin channel");
|
debug!("Using stdin channel");
|
||||||
Ok(TelevisionChannel::Stdin(StdinChannel::new(
|
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 {
|
} else if let Some(prompt) = args.autocomplete_prompt {
|
||||||
let channel = guess_channel_from_prompt(
|
let channel = guess_channel_from_prompt(
|
||||||
@ -175,7 +188,7 @@ mod tests {
|
|||||||
&args,
|
&args,
|
||||||
&config,
|
&config,
|
||||||
true,
|
true,
|
||||||
&TelevisionChannel::Stdin(StdinChannel::new(None)),
|
&TelevisionChannel::Stdin(StdinChannel::new(PreviewType::None)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
|
use crate::cli::args::Shell as CliShell;
|
||||||
use crate::config::shell_integration::ShellIntegrationConfig;
|
use crate::config::shell_integration::ShellIntegrationConfig;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub enum Shell {
|
pub enum Shell {
|
||||||
Bash,
|
Bash,
|
||||||
@ -9,6 +11,30 @@ pub enum Shell {
|
|||||||
Cmd,
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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_ZSH: &str = include_str!("shell/completion.zsh");
|
||||||
const COMPLETION_BASH: &str = include_str!("shell/completion.bash");
|
const COMPLETION_BASH: &str = include_str!("shell/completion.bash");
|
||||||
const COMPLETION_FISH: &str = include_str!("shell/completion.fish");
|
const COMPLETION_FISH: &str = include_str!("shell/completion.fish");
|
||||||
|
Loading…
x
Reference in New Issue
Block a user