feat(cable): support cable channel invocation through the cli (#116)

This commit is contained in:
Alexandre Pasmantier 2024-12-14 17:47:36 +01:00 committed by GitHub
parent 913aa85af0
commit 937d0f0758
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 140 additions and 23 deletions

View File

@ -635,7 +635,6 @@ pub fn results_list_benchmark(c: &mut Criterion) {
result_line_number_fg: Color::Indexed(222),
result_selected_bg: Color::Indexed(222),
match_foreground_color: Color::Indexed(222),
pointer_fg: Color::Indexed(222),
};
c.bench_function("results_list", |b| {

View File

@ -4,7 +4,7 @@ use std::{
ops::Deref,
};
#[derive(Clone, Debug, serde::Deserialize)]
#[derive(Clone, Debug, serde::Deserialize, PartialEq)]
pub struct CableChannelPrototype {
pub name: String,
pub source_command: String,

View File

@ -56,7 +56,7 @@ fn impl_cli_channel(ast: &syn::DeriveInput) -> TokenStream {
);
// create the CliTvChannel enum
let cli_enum_variants = variants
let cli_enum_variants: Vec<_> = variants
.iter()
.filter(|variant| !has_attribute(&variant.attrs, EXCLUDE_FROM_CLI))
.map(|variant| {
@ -64,7 +64,22 @@ fn impl_cli_channel(ast: &syn::DeriveInput) -> TokenStream {
quote! {
#variant_name
}
});
})
.collect();
// Generate lowercase mappings for the TryFrom implementation
let lowercase_match_arms: Vec<_> = variants
.iter()
.filter(|variant| !has_attribute(&variant.attrs, EXCLUDE_FROM_CLI))
.map(|variant| {
let variant_name = &variant.ident;
let lowercase_name = variant_name.to_string().to_lowercase();
quote! {
#lowercase_name => Ok(Self::#variant_name),
}
})
.collect();
let cli_enum = quote! {
use clap::ValueEnum;
use serde::{Deserialize, Serialize};
@ -76,6 +91,19 @@ fn impl_cli_channel(ast: &syn::DeriveInput) -> TokenStream {
#[default]
#(#cli_enum_variants),*
}
impl std::convert::TryFrom<&str> for CliTvChannel {
type Error = String;
fn try_from(channel: &str) -> Result<Self, Self::Error> {
match channel {
#(
#lowercase_match_arms
)*
_ => Err(format!("Invalid cli channel name: {}", channel)),
}
}
}
};
// Generate the match arms for the `to_channel` method
@ -110,6 +138,14 @@ fn impl_cli_channel(ast: &syn::DeriveInput) -> TokenStream {
#(#arms),*
}
}
pub fn all_channels() -> Vec<String> {
vec![
#(
stringify!(#cli_enum_variants).to_lowercase(),
)*
]
}
}
};

View File

@ -1,14 +1,21 @@
use clap::Parser;
use clap::{Parser, Subcommand};
use color_eyre::{eyre::eyre, Result};
use crate::config::{get_config_dir, get_data_dir};
use television_channels::{channels::CliTvChannel, entry::PreviewCommand};
use crate::{
cable,
config::{get_config_dir, get_data_dir},
};
use television_channels::{
cable::CableChannelPrototype, channels::CliTvChannel,
entry::PreviewCommand,
};
#[derive(Parser, Debug)]
#[command(author, version = version(), about)]
pub struct Cli {
/// Which channel shall we watch?
#[arg(value_enum, default_value = "files")]
pub channel: CliTvChannel,
#[arg(value_enum, default_value = "files", value_parser = channel_parser)]
pub channel: ParsedCliChannel,
/// Use a custom preview command (currently only supported by the stdin channel)
#[arg(short, long, value_name = "STRING")]
@ -32,15 +39,25 @@ pub struct Cli {
/// to be handled by the parent process.
#[arg(long, value_name = "STRING")]
pub passthrough_keybindings: Option<String>,
#[command(subcommand)]
command: Option<Command>,
}
#[derive(Subcommand, Debug)]
pub enum Command {
/// Lists available channels
ListChannels,
}
#[derive(Debug)]
pub struct PostProcessedCli {
pub channel: CliTvChannel,
pub channel: ParsedCliChannel,
pub preview_command: Option<PreviewCommand>,
pub tick_rate: f64,
pub frame_rate: f64,
pub passthrough_keybindings: Vec<String>,
pub command: Option<Command>,
}
impl From<Cli> for PostProcessedCli {
@ -63,10 +80,66 @@ impl From<Cli> for PostProcessedCli {
tick_rate: cli.tick_rate,
frame_rate: cli.frame_rate,
passthrough_keybindings,
command: cli.command,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum ParsedCliChannel {
Builtin(CliTvChannel),
Cable(CableChannelPrototype),
}
fn channel_parser(channel: &str) -> Result<ParsedCliChannel> {
let cable_channels = cable::load_cable_channels()?;
CliTvChannel::try_from(channel)
.map(ParsedCliChannel::Builtin)
.or_else(|_| {
cable_channels
.iter()
.find(|(k, _)| k.to_lowercase() == channel)
.map_or_else(
|| Err(eyre!("Unknown channel: {}", channel)),
|(_, v)| Ok(ParsedCliChannel::Cable(v.clone())),
)
})
}
pub fn list_cable_channels() -> Vec<String> {
cable::load_cable_channels()
.unwrap_or_default()
.iter()
.map(|(k, _)| k.clone())
.collect()
}
pub fn list_builtin_channels() -> Vec<String> {
CliTvChannel::all_channels()
.iter()
.map(std::string::ToString::to_string)
.collect()
}
pub fn list_channels() {
println!("\x1b[4mBuiltin channels:\x1b[0m");
for c in list_builtin_channels() {
println!("\t{c}");
}
println!("\n\x1b[4mCustom channels:\x1b[0m");
for c in list_cable_channels().iter().map(|c| c.to_lowercase()) {
println!("\t{c}");
}
}
fn delimiter_parser(s: &str) -> Result<String, String> {
Ok(match s {
"" => ":".to_string(),
"\\t" => "\t".to_string(),
_ => s.to_string(),
})
}
const VERSION_MESSAGE: &str = concat!(
env!("CARGO_PKG_VERSION"),
"\ntarget triple: ",
@ -116,17 +189,21 @@ mod tests {
#[allow(clippy::float_cmp)]
fn test_from_cli() {
let cli = Cli {
channel: CliTvChannel::Files,
channel: ParsedCliChannel::Builtin(CliTvChannel::Files),
preview: Some("bat -n --color=always {}".to_string()),
delimiter: ":".to_string(),
tick_rate: 50.0,
frame_rate: 60.0,
passthrough_keybindings: Some("q,ctrl-w,ctrl-t".to_string()),
command: None,
};
let post_processed_cli: PostProcessedCli = cli.into();
assert_eq!(post_processed_cli.channel, CliTvChannel::Files);
assert_eq!(
post_processed_cli.channel,
ParsedCliChannel::Builtin(CliTvChannel::Files)
);
assert_eq!(
post_processed_cli.preview_command,
Some(PreviewCommand {
@ -142,11 +219,3 @@ mod tests {
);
}
}
fn delimiter_parser(s: &str) -> Result<String, String> {
Ok(match s {
"" => ":".to_string(),
"\\t" => "\t".to_string(),
_ => s.to_string(),
})
}

View File

@ -2,7 +2,7 @@ use std::io::{stdout, IsTerminal, Write};
use std::process::exit;
use clap::Parser;
use cli::PostProcessedCli;
use cli::{list_channels, ParsedCliChannel, PostProcessedCli};
use color_eyre::Result;
use television_channels::channels::TelevisionChannel;
use television_channels::entry::PreviewType;
@ -34,9 +34,17 @@ async fn main() -> Result<()> {
logging::init()?;
let args: PostProcessedCli = Cli::parse().into();
debug!("{:?}", args);
if let Some(command) = args.command {
match command {
cli::Command::ListChannels => {
list_channels();
exit(0);
}
}
}
match App::new(
{
if is_readable_stdin() {
@ -46,7 +54,12 @@ async fn main() -> Result<()> {
))
} else {
debug!("Using {:?} channel", args.channel);
args.channel.to_channel()
match args.channel {
ParsedCliChannel::Builtin(c) => c.to_channel(),
ParsedCliChannel::Cable(c) => {
TelevisionChannel::Cable(c.into())
}
}
}
},
args.tick_rate,