use std::io::Write;
use anyhow::Result;
use clap::Args;
use rpm_spec_analyzer::profile::{MacroEntry, Profile, Provenance};
use super::fmt::{
MAX_MACRO_LABEL_WIDTH, format_macro_value_inline, format_opts, format_provenance,
};
use super::style::Style;
#[derive(Debug, Args)]
pub struct MacrosOpts {
pub profile: Option<String>,
#[arg(long)]
pub filter: Option<String>,
#[arg(long, value_name = "SRC")]
pub source: Option<SourceFilter>,
#[command(flatten)]
pub defines: crate::app::MacroDefinesArg,
}
#[derive(Debug, Clone, Copy, clap::ValueEnum)]
pub enum SourceFilter {
Builtin,
Showrc,
Override,
}
impl SourceFilter {
fn matches(self, prov: &Provenance) -> bool {
match prov {
Provenance::Builtin { .. } => matches!(self, SourceFilter::Builtin),
Provenance::Showrc { .. } => matches!(self, SourceFilter::Showrc),
Provenance::Override => matches!(self, SourceFilter::Override),
}
}
}
pub(super) fn render_macros(
out: &mut impl Write,
profile_name: &str,
profile: &Profile,
opts: &MacrosOpts,
style: &Style,
) -> Result<()> {
let total = profile.macros.entries.len();
let filter_lc = opts.filter.as_deref().map(str::to_ascii_lowercase);
let matched: Vec<(&String, &MacroEntry)> = profile
.macros
.entries
.iter()
.filter(|(name, entry)| {
let name_ok = match &filter_lc {
Some(needle) => name.to_ascii_lowercase().contains(needle),
None => true,
};
let source_ok = match opts.source {
Some(src) => src.matches(&entry.provenance),
None => true,
};
name_ok && source_ok
})
.collect();
let header = match (opts.filter.as_deref(), opts.source) {
(Some(filter), _) => format!("{total} total, {} matching \"{filter}\"", matched.len()),
(None, Some(_)) => format!("{total} total, {} matching --source", matched.len()),
(None, None) => format!("{total} total"),
};
writeln!(
out,
"{} {} {}",
style.bold("# Macros in"),
style.bold_cyan(profile_name),
style.bold(&format!("({header})")),
)?;
if matched.is_empty() {
writeln!(out)?;
writeln!(out, " {}", style.dim("(no macros)"))?;
return Ok(());
}
writeln!(out)?;
let name_width = matched
.iter()
.map(|(n, e)| n.len() + format_opts(e.opts.as_deref()).len())
.max()
.unwrap_or(0)
.min(MAX_MACRO_LABEL_WIDTH);
for (name, entry) in matched {
let opts_str = format_opts(entry.opts.as_deref());
let label = format!("{name}{opts_str}");
writeln!(
out,
" {label:<name_width$} = {} {}",
format_macro_value_inline(&entry.value),
style.dim(&format!("[{}]", format_provenance(&entry.provenance))),
)?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn source_filter_matches_correct_variant() {
let prov_b = Provenance::Builtin {
profile: "x".into(),
};
let prov_s = Provenance::Showrc {
level: -13,
path: None,
};
let prov_o = Provenance::Override;
assert!(SourceFilter::Builtin.matches(&prov_b));
assert!(!SourceFilter::Builtin.matches(&prov_s));
assert!(SourceFilter::Showrc.matches(&prov_s));
assert!(!SourceFilter::Showrc.matches(&prov_o));
assert!(SourceFilter::Override.matches(&prov_o));
}
}