mirror of
https://github.com/alexpasmantier/television.git
synced 2025-06-07 20:15:23 +00:00
feat(cli): allow passing builtin previewers through the cli (e.g. --preview ':files:'
)
This commit is contained in:
parent
1e4c34fecd
commit
2c2ec0e3bb
@ -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<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);
|
||||
let re = Regex::new(r"^\:(\w+)\:$").unwrap();
|
||||
if let Some(captures) = re.captures(&command.command) {
|
||||
|
@ -18,7 +18,7 @@ pub struct 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 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())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<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)]
|
||||
pub struct PostProcessedCli {
|
||||
pub channel: ParsedCliChannel,
|
||||
pub preview_command: Option<PreviewCommand>,
|
||||
pub preview_kind: PreviewKind,
|
||||
pub no_preview: bool,
|
||||
pub tick_rate: Option<f64>,
|
||||
pub frame_rate: Option<f64>,
|
||||
@ -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<Cli> for PostProcessedCli {
|
||||
.map(std::string::ToString::to_string)
|
||||
.collect();
|
||||
|
||||
let preview_command = cli.preview.map(|preview| PreviewCommand {
|
||||
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<String>;
|
||||
@ -98,7 +93,7 @@ impl From<Cli> 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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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)),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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<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_BASH: &str = include_str!("shell/completion.bash");
|
||||
const COMPLETION_FISH: &str = include_str!("shell/completion.fish");
|
||||
|
Loading…
x
Reference in New Issue
Block a user