use crate::cli::OutputFormat;
use crate::commands::common::{fetch_fullname_from_url, open_url, parse_tags};
use crate::config::{Config, DiscourseConfig, save_config};
use anyhow::{Context, Result};
use std::collections::HashMap;
use std::io;
use std::path::Path;
pub fn list_tidy(config_path: &Path, config: &mut Config) -> Result<()> {
let mut missing_report: HashMap<String, Vec<&'static str>> = HashMap::new();
for d in &config.discourse {
let mut missing = Vec::new();
if d.baseurl.trim().is_empty() {
missing.push("baseurl");
}
if d.apikey.is_none() {
missing.push("apikey");
}
if d.api_username.is_none() {
missing.push("api_username");
}
if d.tags.is_none() {
missing.push("tags");
}
if d.ssh_host.is_none() {
missing.push("ssh_host");
}
if d.changelog_topic_id.is_none() {
missing.push("changelog_topic_id");
}
if !missing.is_empty() {
missing_report.insert(d.name.clone(), missing);
}
}
for d in &mut config.discourse {
if d.apikey.is_none() {
d.apikey = Some("".to_string());
}
if d.api_username.is_none() {
d.api_username = Some("".to_string());
}
if d.tags.is_none() {
d.tags = Some(Vec::new());
}
if d.changelog_topic_id.is_none() {
d.changelog_topic_id = Some(0);
}
if d.ssh_host.is_none() {
d.ssh_host = Some("".to_string());
}
if d.fullname.is_none() && !d.baseurl.trim().is_empty() {
d.fullname = fetch_fullname_from_url(&d.baseurl);
}
}
config.discourse.sort_by(|a, b| {
a.name
.to_ascii_lowercase()
.cmp(&b.name.to_ascii_lowercase())
.then_with(|| a.name.cmp(&b.name))
});
save_config(config_path, config)?;
for d in &config.discourse {
if let Some(fields) = missing_report.get(&d.name) {
println!("{}: missing {}", d.name, fields.join(", "));
}
}
Ok(())
}
pub fn list_discourses(
config: &Config,
format: OutputFormat,
tags: Option<&str>,
open: bool,
verbose: bool,
) -> Result<()> {
let filter = tags.map(parse_tags).unwrap_or_default();
let matches_filter = |disc: &DiscourseConfig| {
if filter.is_empty() {
return true;
}
let disc_tags = disc.tags.as_ref().map(|t| {
t.iter()
.map(|tag| tag.to_ascii_lowercase())
.collect::<Vec<_>>()
});
let Some(disc_tags) = disc_tags else {
return false;
};
filter.iter().any(|tag| {
let tag = tag.to_ascii_lowercase();
disc_tags.iter().any(|t| t == &tag)
})
};
let filtered: Vec<_> = config
.discourse
.iter()
.filter(|d| matches_filter(d))
.collect();
if open {
open_discourse_urls(&filtered)?;
}
match format {
OutputFormat::Text => {
if filtered.is_empty() && !verbose {
println!("No discourses found.");
return Ok(());
}
for d in filtered.iter().copied() {
let fullname = d.fullname.as_deref().unwrap_or("");
if fullname.is_empty() {
println!("{} - {}", d.name, d.baseurl);
} else {
println!("{} - {} - {}", d.name, fullname, d.baseurl);
}
}
}
OutputFormat::Markdown => {
for d in filtered.iter().copied() {
let fullname = d.fullname.as_deref().unwrap_or("");
if fullname.is_empty() {
println!("- {} ({})", d.name, d.baseurl);
} else {
println!("- {} ({}) - {}", d.name, fullname, d.baseurl);
}
}
}
OutputFormat::MarkdownTable => {
println!("| Name | Full Name | Base URL |");
println!("| --- | --- | --- |");
for d in filtered.iter().copied() {
let fullname = d.fullname.as_deref().unwrap_or("");
println!("| {} | {} | {} |", d.name, fullname, d.baseurl);
}
}
OutputFormat::Json => {
let raw = serde_json::to_string_pretty(&filtered)?;
println!("{}", raw);
}
OutputFormat::Yaml => {
let raw = serde_yaml::to_string(&filtered)?;
println!("{}", raw);
}
OutputFormat::Csv => {
let mut writer = csv::Writer::from_writer(io::stdout());
writer.write_record(["name", "fullname", "baseurl", "tags"])?;
for d in filtered.iter().copied() {
let tags = d.tags.as_ref().map(|t| t.join(";")).unwrap_or_default();
let fullname = d.fullname.as_deref().unwrap_or("");
writer.write_record([d.name.as_str(), fullname, d.baseurl.as_str(), &tags])?;
}
writer.flush()?;
}
OutputFormat::Urls => {
for d in filtered.iter().copied() {
println!("{}", d.baseurl);
}
}
}
Ok(())
}
fn open_discourse_urls(discourses: &[&DiscourseConfig]) -> Result<()> {
for discourse in discourses {
open_url(&discourse.baseurl)
.with_context(|| format!("opening browser for '{}'", discourse.baseurl))?;
}
Ok(())
}