feat(config): add configuration management for Yarr TUI app
Some checks failed
build / checks-matrix (push) Successful in 19m23s
build / checks-build (push) Has been cancelled
docs / docs (push) Has been cancelled
build / codecov (push) Has been cancelled

This commit is contained in:
uttarayan21
2025-10-08 15:45:08 +05:30
parent 1be1e19c43
commit 48e26332a3
3 changed files with 371 additions and 0 deletions

165
README.md Normal file
View File

@@ -0,0 +1,165 @@
# Yarr
A Terminal User Interface (TUI) for managing Sonarr.
## Features
- View system status and health
- Browse series and episodes
- Monitor download queue
- View download history
- Interactive TUI interface
- Configurable via config files, environment variables, or CLI arguments
## Installation
```bash
cargo install --path .
```
## Configuration
Yarr supports multiple configuration methods with the following priority order (highest to lowest):
1. Command line arguments
2. Environment variables
3. Configuration file
4. Default values
### Configuration File
Create a configuration file in one of these locations:
- `./yarr.toml` (current directory)
- `~/.config/yarr/config.toml` (user config directory)
Example configuration:
```toml
[sonarr]
url = "http://localhost:8989"
api_key = "your-api-key-here"
```
### Environment Variables
Set these environment variables:
```bash
export YARR_SONARR_URL="http://localhost:8989"
export YARR_SONARR_API_KEY="your-api-key-here"
```
### Command Line Arguments
```bash
yarr --sonarr-url="http://localhost:8989" --sonarr-api-key="your-api-key"
```
## Usage
### TUI Mode (Default)
Launch the interactive TUI:
```bash
yarr
# or explicitly
yarr tui
```
### Command Line Mode
List all series:
```bash
yarr list
```
List only monitored series:
```bash
yarr list --monitored
```
Add a new series:
```bash
yarr add --name "Series Name"
```
### Configuration Management
Create a sample config file:
```bash
yarr config init
```
Create config file at specific location:
```bash
yarr config init --path /path/to/config.toml
```
Show current configuration:
```bash
yarr config show
```
Show configuration file search paths:
```bash
yarr config paths
```
### Shell Completions
Generate shell completions:
```bash
# Bash
yarr completions bash > /etc/bash_completion.d/yarr
# Zsh
yarr completions zsh > ~/.zfunc/_yarr
# Fish
yarr completions fish > ~/.config/fish/completions/yarr.fish
# PowerShell
yarr completions powershell > yarr.ps1
```
## TUI Controls
- `q` - Quit
- `↑/↓` or `j/k` - Navigate up/down
- `Enter` - Select/expand
- `Tab` - Switch between panels
- `r` - Refresh data
## Getting Started
1. Install yarr
2. Create a configuration file:
```bash
yarr config init
```
3. Edit the configuration file to set your Sonarr URL and API key
4. Launch the TUI:
```bash
yarr
```
## Finding Your Sonarr API Key
1. Open your Sonarr web interface
2. Go to Settings > General
3. Find the "Security" section
4. Copy the "API Key" value
## License
MIT

186
src/config.rs Normal file
View File

@@ -0,0 +1,186 @@
use config::{Config, ConfigError, Environment, File};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AppConfig {
pub sonarr: SonarrConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SonarrConfig {
pub url: String,
pub api_key: String,
}
impl Default for AppConfig {
fn default() -> Self {
Self {
sonarr: SonarrConfig {
url: "http://localhost:8989".to_string(),
api_key: String::new(),
},
}
}
}
impl AppConfig {
/// Load configuration from various sources with the following priority:
/// 1. Command line arguments (highest priority)
/// 2. Environment variables
/// 3. Config file
/// 4. Default values (lowest priority)
pub fn load(
config_path: Option<PathBuf>,
sonarr_url: Option<String>,
sonarr_api_key: Option<String>,
) -> Result<Self, ConfigError> {
let mut builder = Config::builder();
// Start with default config
builder = builder.add_source(Config::try_from(&AppConfig::default())?);
// Add config file if it exists
if let Some(path) = config_path {
if path.exists() {
builder = builder.add_source(File::from(path));
}
} else {
// Try to load from default locations
if let Some(config_dir) = dirs::config_dir() {
let yarr_config = config_dir.join("yarr").join("config.toml");
if yarr_config.exists() {
builder = builder.add_source(File::from(yarr_config));
}
}
// Also try current directory
let local_config = std::env::current_dir()
.map(|dir| dir.join("yarr.toml"))
.unwrap_or_else(|_| PathBuf::from("yarr.toml"));
if local_config.exists() {
builder = builder.add_source(File::from(local_config));
}
}
// Add environment variables with YARR_ prefix
builder = builder.add_source(
Environment::with_prefix("YARR")
.try_parsing(true)
.separator("_"),
);
// Override with command line arguments if provided
let mut config = builder.build()?.try_deserialize::<AppConfig>()?;
if let Some(url) = sonarr_url {
config.sonarr.url = url;
}
if let Some(api_key) = sonarr_api_key {
config.sonarr.api_key = api_key;
}
Ok(config)
}
/// Create a sample config file
pub fn create_sample_config(path: &PathBuf) -> Result<(), Box<dyn std::error::Error>> {
let sample_config = AppConfig {
sonarr: SonarrConfig {
url: "http://localhost:8989".to_string(),
api_key: "your-api-key-here".to_string(),
},
};
let toml_content = toml::to_string_pretty(&sample_config)?;
// Create directory if it doesn't exist
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
std::fs::write(path, toml_content)?;
Ok(())
}
/// Validate the configuration
pub fn validate(&self) -> Result<(), String> {
if self.sonarr.url.is_empty() {
return Err("Sonarr URL is required".to_string());
}
if self.sonarr.api_key.is_empty() {
return Err("Sonarr API key is required".to_string());
}
// Validate URL format
if !self.sonarr.url.starts_with("http://") && !self.sonarr.url.starts_with("https://") {
return Err("Sonarr URL must start with http:// or https://".to_string());
}
Ok(())
}
/// Get the default config file paths
pub fn get_default_config_paths() -> Vec<PathBuf> {
let mut paths = Vec::new();
// Current directory
if let Ok(current_dir) = std::env::current_dir() {
paths.push(current_dir.join("yarr.toml"));
}
// User config directory
if let Some(config_dir) = dirs::config_dir() {
paths.push(config_dir.join("yarr").join("config.toml"));
}
paths
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::env;
#[test]
fn test_default_config() {
let config = AppConfig::default();
assert_eq!(config.sonarr.url, "http://localhost:8989");
assert_eq!(config.sonarr.api_key, "");
}
#[test]
fn test_config_validation() {
let mut config = AppConfig::default();
// Should fail validation with empty API key
assert!(config.validate().is_err());
// Should fail with invalid URL
config.sonarr.api_key = "test-key".to_string();
config.sonarr.url = "invalid-url".to_string();
assert!(config.validate().is_err());
// Should pass with valid config
config.sonarr.url = "https://example.com".to_string();
assert!(config.validate().is_ok());
}
#[test]
fn test_config_override() {
// Test that CLI args override config
let config = AppConfig::load(
None,
Some("https://cli-url.com".to_string()),
Some("cli-api-key".to_string()),
)
.unwrap();
assert_eq!(config.sonarr.url, "https://cli-url.com");
assert_eq!(config.sonarr.api_key, "cli-api-key");
}
}

20
yarr.toml.example Normal file
View File

@@ -0,0 +1,20 @@
# Yarr Configuration File
# Copy this file to one of the following locations:
# - ./yarr.toml (current directory)
# - ~/.config/yarr/config.toml (user config directory)
[sonarr]
# Sonarr server URL (required)
# Example: "http://localhost:8989" or "https://sonarr.example.com"
url = "http://localhost:8989"
# Sonarr API key (required)
# You can find this in Sonarr under Settings > General > Security > API Key
api_key = "your-api-key-here"
# Environment variables can also be used:
# YARR_SONARR_URL="http://localhost:8989"
# YARR_SONARR_API_KEY="your-api-key-here"
#
# Command line arguments take highest priority:
# yarr --sonarr-url="http://localhost:8989" --sonarr-api-key="your-key"