mirror of
https://github.com/alexpasmantier/television.git
synced 2025-06-07 12:05:34 +00:00
feat(cable): support cable channel invocation through the cli (#116)
This commit is contained in:
parent
913aa85af0
commit
937d0f0758
@ -635,7 +635,6 @@ pub fn results_list_benchmark(c: &mut Criterion) {
|
|||||||
result_line_number_fg: Color::Indexed(222),
|
result_line_number_fg: Color::Indexed(222),
|
||||||
result_selected_bg: Color::Indexed(222),
|
result_selected_bg: Color::Indexed(222),
|
||||||
match_foreground_color: Color::Indexed(222),
|
match_foreground_color: Color::Indexed(222),
|
||||||
pointer_fg: Color::Indexed(222),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
c.bench_function("results_list", |b| {
|
c.bench_function("results_list", |b| {
|
||||||
|
@ -4,7 +4,7 @@ use std::{
|
|||||||
ops::Deref,
|
ops::Deref,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Debug, serde::Deserialize)]
|
#[derive(Clone, Debug, serde::Deserialize, PartialEq)]
|
||||||
pub struct CableChannelPrototype {
|
pub struct CableChannelPrototype {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub source_command: String,
|
pub source_command: String,
|
||||||
|
@ -56,7 +56,7 @@ fn impl_cli_channel(ast: &syn::DeriveInput) -> TokenStream {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// create the CliTvChannel enum
|
// create the CliTvChannel enum
|
||||||
let cli_enum_variants = variants
|
let cli_enum_variants: Vec<_> = variants
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|variant| !has_attribute(&variant.attrs, EXCLUDE_FROM_CLI))
|
.filter(|variant| !has_attribute(&variant.attrs, EXCLUDE_FROM_CLI))
|
||||||
.map(|variant| {
|
.map(|variant| {
|
||||||
@ -64,7 +64,22 @@ fn impl_cli_channel(ast: &syn::DeriveInput) -> TokenStream {
|
|||||||
quote! {
|
quote! {
|
||||||
#variant_name
|
#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! {
|
let cli_enum = quote! {
|
||||||
use clap::ValueEnum;
|
use clap::ValueEnum;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@ -76,6 +91,19 @@ fn impl_cli_channel(ast: &syn::DeriveInput) -> TokenStream {
|
|||||||
#[default]
|
#[default]
|
||||||
#(#cli_enum_variants),*
|
#(#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
|
// Generate the match arms for the `to_channel` method
|
||||||
@ -110,6 +138,14 @@ fn impl_cli_channel(ast: &syn::DeriveInput) -> TokenStream {
|
|||||||
#(#arms),*
|
#(#arms),*
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn all_channels() -> Vec<String> {
|
||||||
|
vec![
|
||||||
|
#(
|
||||||
|
stringify!(#cli_enum_variants).to_lowercase(),
|
||||||
|
)*
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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 crate::{
|
||||||
use television_channels::{channels::CliTvChannel, entry::PreviewCommand};
|
cable,
|
||||||
|
config::{get_config_dir, get_data_dir},
|
||||||
|
};
|
||||||
|
use television_channels::{
|
||||||
|
cable::CableChannelPrototype, channels::CliTvChannel,
|
||||||
|
entry::PreviewCommand,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(author, version = version(), about)]
|
#[command(author, version = version(), about)]
|
||||||
pub struct Cli {
|
pub struct Cli {
|
||||||
/// Which channel shall we watch?
|
/// Which channel shall we watch?
|
||||||
#[arg(value_enum, default_value = "files")]
|
#[arg(value_enum, default_value = "files", value_parser = channel_parser)]
|
||||||
pub channel: CliTvChannel,
|
pub channel: ParsedCliChannel,
|
||||||
|
|
||||||
/// Use a custom preview command (currently only supported by the stdin channel)
|
/// Use a custom preview command (currently only supported by the stdin channel)
|
||||||
#[arg(short, long, value_name = "STRING")]
|
#[arg(short, long, value_name = "STRING")]
|
||||||
@ -32,15 +39,25 @@ pub struct Cli {
|
|||||||
/// to be handled by the parent process.
|
/// to be handled by the parent process.
|
||||||
#[arg(long, value_name = "STRING")]
|
#[arg(long, value_name = "STRING")]
|
||||||
pub passthrough_keybindings: Option<String>,
|
pub passthrough_keybindings: Option<String>,
|
||||||
|
|
||||||
|
#[command(subcommand)]
|
||||||
|
command: Option<Command>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Subcommand, Debug)]
|
||||||
|
pub enum Command {
|
||||||
|
/// Lists available channels
|
||||||
|
ListChannels,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct PostProcessedCli {
|
pub struct PostProcessedCli {
|
||||||
pub channel: CliTvChannel,
|
pub channel: ParsedCliChannel,
|
||||||
pub preview_command: Option<PreviewCommand>,
|
pub preview_command: Option<PreviewCommand>,
|
||||||
pub tick_rate: f64,
|
pub tick_rate: f64,
|
||||||
pub frame_rate: f64,
|
pub frame_rate: f64,
|
||||||
pub passthrough_keybindings: Vec<String>,
|
pub passthrough_keybindings: Vec<String>,
|
||||||
|
pub command: Option<Command>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Cli> for PostProcessedCli {
|
impl From<Cli> for PostProcessedCli {
|
||||||
@ -63,10 +80,66 @@ impl From<Cli> for PostProcessedCli {
|
|||||||
tick_rate: cli.tick_rate,
|
tick_rate: cli.tick_rate,
|
||||||
frame_rate: cli.frame_rate,
|
frame_rate: cli.frame_rate,
|
||||||
passthrough_keybindings,
|
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!(
|
const VERSION_MESSAGE: &str = concat!(
|
||||||
env!("CARGO_PKG_VERSION"),
|
env!("CARGO_PKG_VERSION"),
|
||||||
"\ntarget triple: ",
|
"\ntarget triple: ",
|
||||||
@ -116,17 +189,21 @@ mod tests {
|
|||||||
#[allow(clippy::float_cmp)]
|
#[allow(clippy::float_cmp)]
|
||||||
fn test_from_cli() {
|
fn test_from_cli() {
|
||||||
let cli = Cli {
|
let cli = Cli {
|
||||||
channel: CliTvChannel::Files,
|
channel: ParsedCliChannel::Builtin(CliTvChannel::Files),
|
||||||
preview: Some("bat -n --color=always {}".to_string()),
|
preview: Some("bat -n --color=always {}".to_string()),
|
||||||
delimiter: ":".to_string(),
|
delimiter: ":".to_string(),
|
||||||
tick_rate: 50.0,
|
tick_rate: 50.0,
|
||||||
frame_rate: 60.0,
|
frame_rate: 60.0,
|
||||||
passthrough_keybindings: Some("q,ctrl-w,ctrl-t".to_string()),
|
passthrough_keybindings: Some("q,ctrl-w,ctrl-t".to_string()),
|
||||||
|
command: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let post_processed_cli: PostProcessedCli = cli.into();
|
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!(
|
assert_eq!(
|
||||||
post_processed_cli.preview_command,
|
post_processed_cli.preview_command,
|
||||||
Some(PreviewCommand {
|
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(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
@ -2,7 +2,7 @@ use std::io::{stdout, IsTerminal, Write};
|
|||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use cli::PostProcessedCli;
|
use cli::{list_channels, ParsedCliChannel, PostProcessedCli};
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use television_channels::channels::TelevisionChannel;
|
use television_channels::channels::TelevisionChannel;
|
||||||
use television_channels::entry::PreviewType;
|
use television_channels::entry::PreviewType;
|
||||||
@ -34,9 +34,17 @@ async fn main() -> Result<()> {
|
|||||||
logging::init()?;
|
logging::init()?;
|
||||||
|
|
||||||
let args: PostProcessedCli = Cli::parse().into();
|
let args: PostProcessedCli = Cli::parse().into();
|
||||||
|
|
||||||
debug!("{:?}", args);
|
debug!("{:?}", args);
|
||||||
|
|
||||||
|
if let Some(command) = args.command {
|
||||||
|
match command {
|
||||||
|
cli::Command::ListChannels => {
|
||||||
|
list_channels();
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
match App::new(
|
match App::new(
|
||||||
{
|
{
|
||||||
if is_readable_stdin() {
|
if is_readable_stdin() {
|
||||||
@ -46,7 +54,12 @@ async fn main() -> Result<()> {
|
|||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
debug!("Using {:?} channel", args.channel);
|
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,
|
args.tick_rate,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user