use crate::prelude::*;
const LEGACY_OUTPUT_DIR: &str = "./output";
#[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, validator: &mut OptionsValidator) {
validator.check_url("indexer_url", &self.indexer_url);
validator.check_url("announce_url", &self.announce_url);
validator.check_non_empty("content", &self.content);
for dir in self.content_paths() {
validator.check_dir_exists("content", &dir);
}
let output = self.output_path();
validator.check_dir_exists("output", &output);
if !output.is_dir() && PathBuf::from(LEGACY_OUTPUT_DIR).is_dir() {
let default_dir = PathManager::default_output_dir();
validator.push(OptionIssue::default_changed(
"output",
&self.output.to_string_lossy(),
&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()
),
));
}
}
}