mirror of
https://github.com/alexpasmantier/television.git
synced 2025-07-29 06:11:37 +00:00
feat(cli): add cli options to override configuration and cable directories
`tv --config-dir=/my/dir --cable-dir=/my/other/dir`
This commit is contained in:
parent
666254498e
commit
bc8d636005
@ -435,7 +435,8 @@ pub fn draw(c: &mut Criterion) {
|
||||
b.to_async(&rt).iter_batched(
|
||||
// FIXME: this is kind of hacky
|
||||
|| {
|
||||
let config = Config::new(&ConfigEnv::init().unwrap()).unwrap();
|
||||
let config =
|
||||
Config::new(&ConfigEnv::init().unwrap(), None).unwrap();
|
||||
let backend = TestBackend::new(width, height);
|
||||
let terminal = Terminal::new(backend).unwrap();
|
||||
let (tx, _) = tokio::sync::mpsc::unbounded_channel();
|
||||
|
@ -118,7 +118,7 @@ where
|
||||
/// Load cable channels from the config directory.
|
||||
///
|
||||
/// Cable is loaded by compiling all files located in the `cable/` subdirectory
|
||||
/// of the user's configuration directory (+ defaults)
|
||||
/// of the user's configuration directory, unless a custom directory is provided.
|
||||
///
|
||||
/// # Example:
|
||||
/// ```ignore
|
||||
@ -129,9 +129,15 @@ where
|
||||
/// ├── channel_2.toml
|
||||
/// └── ...
|
||||
/// ```
|
||||
pub fn load_cable() -> Option<Cable> {
|
||||
let cable_dir = get_config_dir().join(CABLE_DIR_NAME);
|
||||
debug!("Cable directory: {}", cable_dir.to_string_lossy());
|
||||
pub fn load_cable<P>(cable_dir: Option<P>) -> Option<Cable>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let cable_dir = match cable_dir {
|
||||
Some(dir) => PathBuf::from(dir.as_ref()),
|
||||
None => get_config_dir().join(CABLE_DIR_NAME),
|
||||
};
|
||||
debug!("Using cable directory: {}", cable_dir.to_string_lossy());
|
||||
let cable_files = get_cable_files(&cable_dir);
|
||||
debug!("Found cable channel files: {:?}", cable_files);
|
||||
|
||||
|
@ -146,6 +146,14 @@ pub struct Cli {
|
||||
)]
|
||||
pub ui_scale: u16,
|
||||
|
||||
/// Provide a custom configuration file to use.
|
||||
#[arg(long, value_name = "PATH", verbatim_doc_comment)]
|
||||
pub config_file: Option<String>,
|
||||
|
||||
/// Provide a custom cable directory to use.
|
||||
#[arg(long, value_name = "PATH", verbatim_doc_comment)]
|
||||
pub cable_dir: Option<String>,
|
||||
|
||||
#[command(subcommand)]
|
||||
pub command: Option<Command>,
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::path::Path;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::{Result, anyhow};
|
||||
use tracing::debug;
|
||||
@ -11,6 +11,7 @@ use crate::{
|
||||
},
|
||||
cli::args::{Cli, Command},
|
||||
config::{KeyBindings, get_config_dir, get_data_dir},
|
||||
utils::paths::expand_tilde,
|
||||
};
|
||||
|
||||
pub mod args;
|
||||
@ -34,6 +35,8 @@ pub struct PostProcessedCli {
|
||||
pub no_remote: bool,
|
||||
pub no_help: bool,
|
||||
pub ui_scale: u16,
|
||||
pub config_file: Option<PathBuf>,
|
||||
pub cable_dir: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl Default for PostProcessedCli {
|
||||
@ -55,6 +58,8 @@ impl Default for PostProcessedCli {
|
||||
no_remote: false,
|
||||
no_help: false,
|
||||
ui_scale: 100,
|
||||
config_file: None,
|
||||
cable_dir: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -115,6 +120,8 @@ pub fn post_process(cli: Cli) -> PostProcessedCli {
|
||||
no_remote: cli.no_remote,
|
||||
no_help: cli.no_help,
|
||||
ui_scale: cli.ui_scale,
|
||||
config_file: cli.config_file.map(expand_tilde),
|
||||
cable_dir: cli.cable_dir.map(expand_tilde),
|
||||
}
|
||||
}
|
||||
|
||||
@ -149,8 +156,9 @@ fn parse_keybindings_literal(
|
||||
toml::from_str(&toml_definition).map_err(|e| anyhow!(e))
|
||||
}
|
||||
|
||||
pub fn list_channels() {
|
||||
let channels = cable::load_cable().expect("Failed to load cable channels");
|
||||
pub fn list_channels(cable_dir: Option<&Path>) {
|
||||
let channels = cable::load_cable::<&Path>(cable_dir)
|
||||
.expect("Failed to load cable channels");
|
||||
for c in channels.keys() {
|
||||
println!("\t{c}");
|
||||
}
|
||||
|
@ -122,16 +122,30 @@ const USER_CONFIG_ERROR_MSG: &str = "
|
||||
|
||||
impl Config {
|
||||
#[allow(clippy::missing_panics_doc, clippy::missing_errors_doc)]
|
||||
pub fn new(config_env: &ConfigEnv) -> Result<Self> {
|
||||
pub fn new(
|
||||
config_env: &ConfigEnv,
|
||||
custom_config_file: Option<&Path>,
|
||||
) -> Result<Self> {
|
||||
// Load the default_config values as base defaults
|
||||
let default_config: Config = default_config_from_file()?;
|
||||
|
||||
// if a config file exists, load it and merge it with the default configuration
|
||||
if config_env.config_dir.join(CONFIG_FILE_NAME).is_file() {
|
||||
debug!("Found config file at {:?}", config_env.config_dir);
|
||||
if config_env.config_dir.join(CONFIG_FILE_NAME).is_file()
|
||||
|| custom_config_file.is_some()
|
||||
{
|
||||
let config_file = if let Some(path) = custom_config_file {
|
||||
debug!("Using custom configuration file at: {:?}", path);
|
||||
path.to_path_buf()
|
||||
} else {
|
||||
let config_file = config_env.config_dir.join(CONFIG_FILE_NAME);
|
||||
debug!(
|
||||
"Using default configuration file at: {:?}",
|
||||
config_file
|
||||
);
|
||||
config_file
|
||||
};
|
||||
|
||||
let user_cfg: Config =
|
||||
Self::load_user_config(&config_env.config_dir)?;
|
||||
let user_cfg: Config = Self::load_user_config(&config_file)?;
|
||||
|
||||
// merge the user configuration with the default configuration
|
||||
let final_cfg = Self::merge_with_default(default_config, user_cfg);
|
||||
@ -156,12 +170,11 @@ impl Config {
|
||||
}
|
||||
}
|
||||
|
||||
fn load_user_config(config_dir: &Path) -> Result<Self> {
|
||||
let path = config_dir.join(CONFIG_FILE_NAME);
|
||||
let contents = std::fs::read_to_string(&path)?;
|
||||
fn load_user_config(config_file: &Path) -> Result<Self> {
|
||||
let contents = std::fs::read_to_string(config_file)?;
|
||||
let user_cfg: Config = toml::from_str(&contents).context(format!(
|
||||
"Error parsing configuration file: {}\n{}",
|
||||
path.display(),
|
||||
config_file.display(),
|
||||
USER_CONFIG_ERROR_MSG,
|
||||
))?;
|
||||
Ok(user_cfg)
|
||||
@ -320,7 +333,7 @@ mod tests {
|
||||
let mut file = File::create(&config_file).unwrap();
|
||||
file.write_all(DEFAULT_CONFIG.as_bytes()).unwrap();
|
||||
|
||||
let config = Config::load_user_config(config_dir).unwrap();
|
||||
let config = Config::load_user_config(&config_file).unwrap();
|
||||
assert_eq!(config.application.data_dir, get_data_dir());
|
||||
assert_eq!(config.application.config_dir, get_config_dir());
|
||||
assert_eq!(config, toml::from_str(DEFAULT_CONFIG).unwrap());
|
||||
@ -338,7 +351,7 @@ mod tests {
|
||||
_data_dir: get_data_dir(),
|
||||
config_dir: config_dir.to_path_buf(),
|
||||
};
|
||||
let config = Config::new(&config_env).unwrap();
|
||||
let config = Config::new(&config_env, None).unwrap();
|
||||
let mut default_config: Config =
|
||||
toml::from_str(DEFAULT_CONFIG).unwrap();
|
||||
default_config.shell_integration.merge_triggers();
|
||||
@ -392,7 +405,7 @@ mod tests {
|
||||
_data_dir: get_data_dir(),
|
||||
config_dir: config_dir.to_path_buf(),
|
||||
};
|
||||
let config = Config::new(&config_env).unwrap();
|
||||
let config = Config::new(&config_env, None).unwrap();
|
||||
|
||||
let mut default_config: Config =
|
||||
toml::from_str(DEFAULT_CONFIG).unwrap();
|
||||
@ -452,7 +465,7 @@ mod tests {
|
||||
config_dir: config_dir.to_path_buf(),
|
||||
};
|
||||
|
||||
let config = Config::new(&config_env).unwrap();
|
||||
let config = Config::new(&config_env, None).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
config.shell_integration.commands.iter().collect::<Vec<_>>(),
|
||||
@ -479,7 +492,7 @@ mod tests {
|
||||
config_dir: config_dir.to_path_buf(),
|
||||
};
|
||||
|
||||
let config = Config::new(&config_env).unwrap();
|
||||
let config = Config::new(&config_env, None).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
config.shell_integration.keybindings,
|
||||
|
@ -44,16 +44,17 @@ async fn main() -> Result<()> {
|
||||
|
||||
// load the configuration file
|
||||
debug!("Loading configuration...");
|
||||
let mut config = Config::new(&ConfigEnv::init()?)?;
|
||||
let mut config =
|
||||
Config::new(&ConfigEnv::init()?, args.config_file.as_deref())?;
|
||||
|
||||
// handle subcommands
|
||||
debug!("Handling subcommands...");
|
||||
if let Some(subcommand) = &args.command {
|
||||
handle_subcommand(subcommand, &config)?;
|
||||
handle_subcommand(subcommand, &config, &args)?;
|
||||
}
|
||||
|
||||
debug!("Loading cable channels...");
|
||||
let cable = load_cable().unwrap_or_else(|| exit(1));
|
||||
let cable = load_cable(args.cable_dir.as_ref()).unwrap_or_else(|| exit(1));
|
||||
|
||||
// optionally change the working directory
|
||||
args.working_directory.as_ref().map(set_current_dir);
|
||||
@ -139,10 +140,14 @@ pub fn set_current_dir(path: &String) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn handle_subcommand(command: &Command, config: &Config) -> Result<()> {
|
||||
pub fn handle_subcommand(
|
||||
command: &Command,
|
||||
config: &Config,
|
||||
args: &PostProcessedCli,
|
||||
) -> Result<()> {
|
||||
match command {
|
||||
Command::ListChannels => {
|
||||
list_channels();
|
||||
list_channels(args.cable_dir.as_deref());
|
||||
exit(0);
|
||||
}
|
||||
Command::InitShell { shell } => {
|
||||
|
@ -766,7 +766,7 @@ mod test {
|
||||
use crate::{
|
||||
action::Action,
|
||||
cable::Cable,
|
||||
config::{Binding, KeyBindings},
|
||||
config::Binding,
|
||||
event::Key,
|
||||
television::{MatchingMode, Television},
|
||||
};
|
||||
|
@ -6,6 +6,7 @@ pub mod hashmaps;
|
||||
pub mod indices;
|
||||
pub mod input;
|
||||
pub mod metadata;
|
||||
pub mod paths;
|
||||
pub mod rocell;
|
||||
pub mod shell;
|
||||
pub mod stdin;
|
||||
|
35
television/utils/paths.rs
Normal file
35
television/utils/paths.rs
Normal file
@ -0,0 +1,35 @@
|
||||
use directories::UserDirs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub fn expand_tilde<P>(path: P) -> PathBuf
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let path = path.as_ref();
|
||||
if path.starts_with("~") {
|
||||
let home = UserDirs::new()
|
||||
.map(|dirs| dirs.home_dir().to_path_buf())
|
||||
.unwrap_or_else(|| PathBuf::from("/"));
|
||||
home.join(path.strip_prefix("~").unwrap())
|
||||
} else {
|
||||
path.to_path_buf()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_expand_tilde() {
|
||||
assert_eq!(
|
||||
expand_tilde("~/test").to_str().unwrap(),
|
||||
&format!("{}/test", UserDirs::new().unwrap().home_dir().display())
|
||||
);
|
||||
assert_eq!(expand_tilde("test").to_str().unwrap(), "test");
|
||||
assert_eq!(
|
||||
expand_tilde("/absolute/path").to_str().unwrap(),
|
||||
"/absolute/path"
|
||||
);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user