diff --git a/Cargo.lock b/Cargo.lock
index 6e20081..eaa955d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -159,6 +159,12 @@ dependencies = [
"console",
]
+[[package]]
+name = "bitflags"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
+
[[package]]
name = "bitflags"
version = "2.9.0"
@@ -341,6 +347,12 @@ dependencies = [
"windows-sys 0.59.0",
]
+[[package]]
+name = "comma"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "55b672471b4e9f9e95499ea597ff64941a309b2cdbffcc46f2cc5e2d971fd335"
+
[[package]]
name = "compact_str"
version = "0.8.1"
@@ -461,7 +473,7 @@ version = "0.28.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6"
dependencies = [
- "bitflags",
+ "bitflags 2.9.0",
"crossterm_winapi",
"filedescriptor",
"mio",
@@ -963,7 +975,7 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
dependencies = [
- "bitflags",
+ "bitflags 2.9.0",
"libc",
]
@@ -1019,6 +1031,15 @@ version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
+[[package]]
+name = "memoffset"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
+dependencies = [
+ "autocfg",
+]
+
[[package]]
name = "minimal-lexical"
version = "0.2.1"
@@ -1046,6 +1067,20 @@ dependencies = [
"windows-sys 0.52.0",
]
+[[package]]
+name = "nix"
+version = "0.25.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4"
+dependencies = [
+ "autocfg",
+ "bitflags 1.2.1",
+ "cfg-if",
+ "libc",
+ "memoffset",
+ "pin-utils",
+]
+
[[package]]
name = "nom"
version = "7.1.3"
@@ -1290,7 +1325,7 @@ version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b"
dependencies = [
- "bitflags",
+ "bitflags 2.9.0",
"cassowary",
"compact_str",
"crossterm",
@@ -1332,7 +1367,7 @@ version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3"
dependencies = [
- "bitflags",
+ "bitflags 2.9.0",
]
[[package]]
@@ -1396,6 +1431,19 @@ version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
+[[package]]
+name = "rexpect"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "01ff60778f96fb5a48adbe421d21bf6578ed58c0872d712e7e08593c195adff8"
+dependencies = [
+ "comma",
+ "nix",
+ "regex",
+ "tempfile",
+ "thiserror 1.0.69",
+]
+
[[package]]
name = "ring"
version = "0.17.14"
@@ -1434,7 +1482,7 @@ version = "0.38.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
dependencies = [
- "bitflags",
+ "bitflags 2.9.0",
"errno",
"libc",
"linux-raw-sys 0.4.15",
@@ -1447,7 +1495,7 @@ version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf"
dependencies = [
- "bitflags",
+ "bitflags 2.9.0",
"errno",
"libc",
"linux-raw-sys 0.9.4",
@@ -1742,6 +1790,7 @@ dependencies = [
"nucleo",
"parking_lot",
"ratatui",
+ "rexpect",
"rustc-hash",
"serde",
"serde_json",
@@ -2378,7 +2427,7 @@ version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
dependencies = [
- "bitflags",
+ "bitflags 2.9.0",
]
[[package]]
diff --git a/Cargo.toml b/Cargo.toml
index ae2deef..ed9316d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -75,6 +75,7 @@ clipboard-win = "5.4.0"
[dev-dependencies]
criterion = { version = "0.5", features = ["async_tokio"] }
tempfile = "3.16.0"
+rexpect = "0.5"
[build-dependencies]
diff --git a/television/cable.rs b/television/cable.rs
index 8ca0fa2..99faa78 100644
--- a/television/cable.rs
+++ b/television/cable.rs
@@ -10,7 +10,6 @@ use walkdir::WalkDir;
use crate::{
channels::prototypes::ChannelPrototype, cli::unknown_channel_exit,
- config::get_config_dir,
};
/// A neat `HashMap` of channel prototypes indexed by their name.
@@ -129,16 +128,13 @@ where
/// ├── channel_2.toml
/// └── ...
/// ```
-pub fn load_cable
(cable_dir: Option
) -> Option
+pub fn load_cable(cable_dir: P) -> Option
where
P: AsRef,
{
- let cable_dir = match cable_dir {
- Some(dir) => PathBuf::from(dir.as_ref()),
- None => get_config_dir().join(CABLE_DIR_NAME),
- };
+ let cable_dir = cable_dir.as_ref();
debug!("Using cable directory: {}", cable_dir.to_string_lossy());
- let cable_files = get_cable_files(&cable_dir);
+ let cable_files = get_cable_files(cable_dir);
debug!("Found cable channel files: {:?}", cable_files);
if cable_files.is_empty() {
diff --git a/television/cli/mod.rs b/television/cli/mod.rs
index 1870307..d912606 100644
--- a/television/cli/mod.rs
+++ b/television/cli/mod.rs
@@ -156,9 +156,12 @@ fn parse_keybindings_literal(
toml::from_str(&toml_definition).map_err(|e| anyhow!(e))
}
-pub fn list_channels(cable_dir: Option<&Path>) {
- let channels = cable::load_cable::<&Path>(cable_dir)
- .expect("Failed to load cable channels");
+pub fn list_channels(cable_dir: P)
+where
+ P: AsRef,
+{
+ let channels =
+ cable::load_cable(cable_dir).expect("Failed to load cable channels");
for c in channels.keys() {
println!("\t{c}");
}
diff --git a/television/config/mod.rs b/television/config/mod.rs
index 7c4081d..3753c93 100644
--- a/television/config/mod.rs
+++ b/television/config/mod.rs
@@ -35,6 +35,8 @@ pub struct AppConfig {
pub data_dir: PathBuf,
#[serde(default = "get_config_dir")]
pub config_dir: PathBuf,
+ #[serde(default = "default_cable_dir")]
+ pub cable_dir: PathBuf,
#[serde(default = "default_frame_rate")]
pub frame_rate: f64,
#[serde(default = "default_tick_rate")]
@@ -290,6 +292,10 @@ pub fn get_config_dir() -> PathBuf {
}
}
+fn default_cable_dir() -> PathBuf {
+ get_config_dir().join(CABLE_DIR_NAME)
+}
+
fn project_directory() -> Option {
ProjectDirs::from("com", "", env!("CARGO_PKG_NAME"))
}
diff --git a/television/main.rs b/television/main.rs
index 6bf7792..4a328a2 100644
--- a/television/main.rs
+++ b/television/main.rs
@@ -47,22 +47,23 @@ async fn main() -> Result<()> {
let mut config =
Config::new(&ConfigEnv::init()?, args.config_file.as_deref())?;
+ // override configuration values with provided CLI arguments
+ debug!("Applying CLI overrides...");
+ apply_cli_overrides(&args, &mut config);
+
// handle subcommands
debug!("Handling subcommands...");
if let Some(subcommand) = &args.command {
- handle_subcommand(subcommand, &config, &args)?;
+ handle_subcommand(subcommand, &config)?;
}
debug!("Loading cable channels...");
- let cable = load_cable(args.cable_dir.as_ref()).unwrap_or_else(|| exit(1));
+ let cable =
+ load_cable(&config.application.cable_dir).unwrap_or_else(|| exit(1));
// optionally change the working directory
args.working_directory.as_ref().map(set_current_dir);
- // optionally override configuration values with CLI arguments
- debug!("Applying CLI overrides...");
- apply_cli_overrides(&args, &mut config);
-
// determine the channel to use based on the CLI arguments and configuration
debug!("Determining channel...");
let channel_prototype =
@@ -106,6 +107,9 @@ async fn main() -> Result<()> {
///
/// This function mutates the configuration in place.
fn apply_cli_overrides(args: &PostProcessedCli, config: &mut Config) {
+ if let Some(cable_dir) = &args.cable_dir {
+ config.application.cable_dir.clone_from(cable_dir);
+ }
if let Some(tick_rate) = args.tick_rate {
config.application.tick_rate = tick_rate;
}
@@ -140,14 +144,10 @@ pub fn set_current_dir(path: &String) -> Result<()> {
Ok(())
}
-pub fn handle_subcommand(
- command: &Command,
- config: &Config,
- args: &PostProcessedCli,
-) -> Result<()> {
+pub fn handle_subcommand(command: &Command, config: &Config) -> Result<()> {
match command {
Command::ListChannels => {
- list_channels(args.cable_dir.as_deref());
+ list_channels(&config.application.cable_dir);
exit(0);
}
Command::InitShell { shell } => {
diff --git a/tests/e2e.rs b/tests/e2e.rs
new file mode 100644
index 0000000..b6f4158
--- /dev/null
+++ b/tests/e2e.rs
@@ -0,0 +1,97 @@
+use std::path::Path;
+
+use rexpect::error::Error;
+use rexpect::process::wait::WaitStatus;
+use rexpect::spawn;
+use television::channels::prototypes::ChannelPrototype;
+use tempfile::TempDir;
+
+#[allow(dead_code)]
+fn setup_config(content: &str) -> TempDir {
+ let temp_dir = tempfile::tempdir().unwrap();
+ let config_path = temp_dir.path().join("config.toml");
+ std::fs::write(&config_path, content).unwrap();
+ temp_dir
+}
+
+fn setup_cable_channels(channels: Vec<&str>, cable_dir: D)
+where
+ D: AsRef,
+{
+ let cable_dir = cable_dir.as_ref();
+ std::fs::create_dir_all(cable_dir).unwrap();
+ for channel in channels {
+ let name = toml::from_str::(channel)
+ .unwrap()
+ .metadata
+ .name;
+ let channel_path = cable_dir.join(format!("{}.toml", name));
+ std::fs::write(&channel_path, channel).unwrap();
+ }
+}
+
+#[test]
+fn tv_version() -> Result<(), Error> {
+ let mut p = spawn("./target/debug/tv --version", Some(500))?;
+ p.exp_regex("television [0-9]+\\.[0-9]+\\.[0-9]+")?;
+
+ Ok(())
+}
+
+#[test]
+fn tv_help() -> Result<(), Error> {
+ let mut p = spawn("./target/debug/tv --help", Some(500))?;
+ p.exp_regex("A cross-platform")?;
+
+ Ok(())
+}
+
+const BASIC_FILE_CHANNEL: &str = r#"
+ [metadata]
+ name = "files"
+
+ [source]
+ command = "fd -t f"
+"#;
+
+const BASIC_DIR_CHANNEL: &str = r#"
+ [metadata]
+ name = "dirs"
+
+ [source]
+ command = "fd -t d"
+"#;
+
+#[test]
+fn tv_list_channels() -> Result<(), Error> {
+ let temp_dir = TempDir::new().unwrap();
+ setup_cable_channels(
+ vec![BASIC_FILE_CHANNEL, BASIC_DIR_CHANNEL],
+ &temp_dir,
+ );
+
+ let mut p = spawn(
+ &format!(
+ "./target/debug/tv --cable-dir {} list-channels ",
+ temp_dir.path().display()
+ ),
+ Some(500),
+ )?;
+ p.exp_regex("files")?;
+ p.exp_regex("dirs")?;
+
+ Ok(())
+}
+
+#[test]
+fn tv_init_zsh() -> Result<(), Error> {
+ let p = spawn("./target/debug/tv init zsh", Some(500))?;
+ // check that the process exits successfully
+ if let Ok(w) = p.process.wait() {
+ assert_eq!(w, WaitStatus::Exited(w.pid().unwrap(), 0));
+ } else {
+ panic!("Failed to wait for process");
+ }
+
+ Ok(())
+}