use crate::args::i18n;
use pacsea::theme;
pub fn handle_news(unread: bool, read: bool, all_news: bool) -> ! {
use std::collections::HashSet;
tracing::info!(
unread = unread,
read = read,
all_news = all_news,
"News mode requested from CLI"
);
let show_all = if !unread && !read && !all_news {
tracing::info!("No news option specified, defaulting to --all");
true
} else {
all_news
};
let news_read_path = theme::lists_dir().join("news_read_urls.json");
let read_urls: HashSet<String> = if let Ok(s) = std::fs::read_to_string(&news_read_path)
&& let Ok(set) = serde_json::from_str::<HashSet<String>>(&s)
{
set
} else {
HashSet::new()
};
let (tx, rx) = std::sync::mpsc::channel();
std::thread::spawn(move || {
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build();
let res = match rt {
Ok(rt) => rt.block_on(pacsea::sources::fetch_arch_news(100, None)),
Err(e) => Err::<Vec<pacsea::state::NewsItem>, _>(format!("rt: {e}").into()),
};
let _ = tx.send(res);
});
let news_items = match rx.recv() {
Ok(Ok(items)) => items,
Ok(Err(e)) => {
eprintln!("{}", i18n::t_fmt1("app.cli.news.fetch_error", &e));
tracing::error!(error = %e, "Failed to fetch news");
std::process::exit(1);
}
Err(e) => {
eprintln!("{}", i18n::t_fmt1("app.cli.news.runtime_error", e));
tracing::error!(error = %e, "Failed to receive news from thread");
std::process::exit(1);
}
};
let filtered_items: Vec<&pacsea::state::NewsItem> = if show_all {
news_items.iter().collect()
} else if unread {
news_items
.iter()
.filter(|item| !read_urls.contains(&item.url))
.collect()
} else if read {
news_items
.iter()
.filter(|item| read_urls.contains(&item.url))
.collect()
} else {
news_items.iter().collect()
};
if filtered_items.is_empty() {
println!("{}", i18n::t("app.cli.news.no_items"));
} else {
for item in &filtered_items {
let status = if read_urls.contains(&item.url) {
i18n::t("app.cli.news.status_read")
} else {
i18n::t("app.cli.news.status_unread")
};
println!("{} {} - {}", status, item.date, item.title);
println!("{}", i18n::t_fmt1("app.cli.news.url_label", &item.url));
println!();
}
}
println!("{}", i18n::t("app.cli.news.website_link"));
tracing::info!(count = filtered_items.len(), "Displayed news items");
std::process::exit(0);
}
#[cfg(test)]
mod tests {
use std::collections::HashSet;
#[test]
fn test_news_filtering_defaults_to_all() {
let unread = false;
let read = false;
let all_news = false;
let show_all = if !unread && !read && !all_news {
true
} else {
all_news
};
assert!(show_all, "Should default to all when no flags specified");
}
#[test]
fn test_news_filtering_unread() {
let read_urls: HashSet<String> =
HashSet::from([("https://archlinux.org/news/item-1/").to_string()]);
let news_items = [
pacsea::state::NewsItem {
date: "2025-01-01".to_string(),
title: "Item 1".to_string(),
url: "https://archlinux.org/news/item-1/".to_string(),
},
pacsea::state::NewsItem {
date: "2025-01-02".to_string(),
title: "Item 2".to_string(),
url: "https://archlinux.org/news/item-2/".to_string(),
},
];
let filtered: Vec<&pacsea::state::NewsItem> = news_items
.iter()
.filter(|item| !read_urls.contains(&item.url))
.collect();
assert_eq!(filtered.len(), 1);
assert_eq!(filtered[0].title, "Item 2");
}
#[test]
fn test_news_filtering_read() {
let read_urls: HashSet<String> =
HashSet::from([("https://archlinux.org/news/item-1/").to_string()]);
let news_items = [
pacsea::state::NewsItem {
date: "2025-01-01".to_string(),
title: "Item 1".to_string(),
url: "https://archlinux.org/news/item-1/".to_string(),
},
pacsea::state::NewsItem {
date: "2025-01-02".to_string(),
title: "Item 2".to_string(),
url: "https://archlinux.org/news/item-2/".to_string(),
},
];
let filtered: Vec<&pacsea::state::NewsItem> = news_items
.iter()
.filter(|item| read_urls.contains(&item.url))
.collect();
assert_eq!(filtered.len(), 1);
assert_eq!(filtered[0].title, "Item 1");
}
}