use crate::prelude::*;
use caesura_macros::Options;
use rogue_logging::{TimeFormat, Verbosity};
use serde::{Deserialize, Serialize};
const LEGACY_OUTPUT_DIR: &str = "./output";
pub(crate) const CONTENT_DIR_LABEL: &str = "Content Directory";
pub(crate) const OUTPUT_DIR_LABEL: &str = "Output Directory";
#[derive(Options, Clone, Debug, Deserialize, Serialize)]
pub struct SharedOptions {
#[arg(long)]
#[options(required)]
pub announce_url: String,
#[arg(long)]
#[options(required)]
pub api_key: String,
#[arg(long)]
#[options(required, default_fn = default_indexer, default_doc = "from announce_url")]
pub indexer: String,
#[arg(long)]
#[options(required, default_fn = default_indexer_url, default_doc = "from announce_url")]
pub indexer_url: String,
#[arg(long)]
#[options(required, default_fn = default_content)]
pub content: Vec<PathBuf>,
#[arg(long, value_enum)]
pub verbosity: Verbosity,
#[arg(long)]
pub log_time: TimeFormat,
#[arg(long)]
#[options(default_fn = default_output, default_doc = "`~/.local/share/caesura/output/` or platform equivalent")]
pub output: PathBuf,
}
#[expect(
clippy::unnecessary_wraps,
reason = "Options macro default_fn requires Option<T>"
)]
fn default_output(_partial: &SharedOptionsPartial) -> Option<PathBuf> {
Some(PathManager::default_output_dir())
}
fn default_content(_partial: &SharedOptionsPartial) -> Option<Vec<PathBuf>> {
is_docker().then(|| vec![PathBuf::from("/content")])
}
fn default_indexer(partial: &SharedOptionsPartial) -> Option<String> {
match partial.announce_url.as_deref() {
Some(url) if url.starts_with(RED_TRACKER_URL) => Some("red".to_owned()),
Some(url) if url.starts_with(OPS_TRACKER_URL) => Some("ops".to_owned()),
_ => None,
}
}
fn default_indexer_url(partial: &SharedOptionsPartial) -> Option<String> {
let indexer = partial.indexer.clone().or_else(|| default_indexer(partial));
match indexer.as_deref() {
Some("red") => Some(RED_URL.to_owned()),
Some("ops") => Some(OPS_URL.to_owned()),
_ => None,
}
}
impl SharedOptions {
#[cfg(test)]
pub const MOCK_INDEXER: &'static str = "red";
#[must_use]
pub fn get_indexer(&self) -> Indexer {
Indexer::from(self.indexer.as_str())
}
#[must_use]
pub fn output_path(&self) -> PathBuf {
self.output.expand_tilde()
}
#[must_use]
pub fn content_paths(&self) -> Vec<PathBuf> {
self.content.iter().map(ExpandTilde::expand_tilde).collect()
}
#[cfg(test)]
pub fn mock() -> Self {
Self {
indexer: Self::MOCK_INDEXER.to_owned(),
indexer_url: RED_URL.to_owned(),
announce_url: format!("{RED_TRACKER_URL}/test/announce"),
api_key: "test_api_key".to_owned(),
..SharedOptions::default()
}
}
}
impl OptionsContract for SharedOptions {
type Partial = SharedOptionsPartial;
fn validate(&self, errors: &mut Vec<OptionRule>) {
if !self.indexer_url.starts_with("https://") && !self.indexer_url.starts_with("http://") {
errors.push(UrlNotHttp(
"Indexer URL".to_owned(),
self.indexer_url.clone(),
));
}
if self.indexer_url.ends_with('/') {
errors.push(UrlInvalidSuffix(
"Indexer URL".to_owned(),
self.indexer_url.clone(),
));
}
if !self.announce_url.starts_with("https://") && !self.announce_url.starts_with("http://") {
errors.push(UrlNotHttp(
"Announce URL".to_owned(),
self.announce_url.clone(),
));
}
if self.announce_url.ends_with('/') {
errors.push(UrlInvalidSuffix(
"Announce URL".to_owned(),
self.announce_url.clone(),
));
}
if self.content.is_empty() {
errors.push(IsEmpty(CONTENT_DIR_LABEL.to_owned()));
}
for dir in self.content_paths() {
if !dir.exists() || !dir.is_dir() {
errors.push(DoesNotExist(
CONTENT_DIR_LABEL.to_owned(),
dir.to_string_lossy().to_string(),
));
}
}
let output = self.output_path();
if !output.exists() || !output.is_dir() {
errors.push(DoesNotExist(
OUTPUT_DIR_LABEL.to_owned(),
output.to_string_lossy().to_string(),
));
if PathBuf::from(LEGACY_OUTPUT_DIR).is_dir() {
let default_dir = PathManager::default_output_dir();
errors.push(Changed(
OUTPUT_DIR_LABEL.to_owned(),
self.output.to_string_lossy().to_string(),
format!("In v0.27.0 the default output path changed to {}.\nPass the option: --output {LEGACY_OUTPUT_DIR} to use the previous output path.", default_dir.display()),
));
}
}
}
}