use rpm_spec::ast::{FileEntry, FilesContent, Section, Span, SpecFile, SpecItem};
const MAX_DEPTH: u32 = 128;
pub fn for_each_files_entry<'ast, F>(spec: &'ast SpecFile<Span>, mut f: F)
where
F: FnMut(&'ast FileEntry<Span>),
{
for_each_files_section(spec, |sec| {
walk_entries(sec.content, &mut f);
});
}
pub fn for_each_files_entry_with_subpkg<'ast, F>(spec: &'ast SpecFile<Span>, mut f: F)
where
F: FnMut(Option<&'ast rpm_spec::ast::SubpkgRef>, &'ast FileEntry<Span>),
{
for_each_files_section(spec, |sec| {
let subpkg = sec.subpkg;
walk_entries(sec.content, &mut |e| f(subpkg, e));
});
}
#[derive(Debug)]
pub struct FilesSectionView<'a> {
pub subpkg: Option<&'a rpm_spec::ast::SubpkgRef>,
pub file_lists: &'a [rpm_spec::ast::Text],
pub content: &'a [FilesContent<Span>],
}
pub fn for_each_files_section<'ast, F>(spec: &'ast SpecFile<Span>, mut f: F)
where
F: FnMut(FilesSectionView<'ast>),
{
walk_top_items(&spec.items, &mut f);
}
fn walk_top_items<'ast, F>(items: &'ast [SpecItem<Span>], f: &mut F)
where
F: FnMut(FilesSectionView<'ast>),
{
walk_top_items_inner(items, f, 0);
}
fn walk_top_items_inner<'ast, F>(items: &'ast [SpecItem<Span>], f: &mut F, depth: u32)
where
F: FnMut(FilesSectionView<'ast>),
{
if depth >= MAX_DEPTH {
return;
}
for item in items {
match item {
SpecItem::Section(boxed) => {
if let Section::Files {
subpkg,
file_lists,
content,
..
} = boxed.as_ref()
{
f(FilesSectionView {
subpkg: subpkg.as_ref(),
file_lists,
content,
});
}
}
SpecItem::Conditional(c) => {
for branch in &c.branches {
walk_top_items_inner(&branch.body, f, depth + 1);
}
if let Some(els) = &c.otherwise {
walk_top_items_inner(els, f, depth + 1);
}
}
_ => {}
}
}
}
fn walk_entries<'ast, F>(items: &'ast [FilesContent<Span>], f: &mut F)
where
F: FnMut(&'ast FileEntry<Span>),
{
walk_entries_inner(items, f, 0);
}
fn walk_entries_inner<'ast, F>(items: &'ast [FilesContent<Span>], f: &mut F, depth: u32)
where
F: FnMut(&'ast FileEntry<Span>),
{
if depth >= MAX_DEPTH {
return;
}
for item in items {
match item {
FilesContent::Entry(e) => f(e),
FilesContent::Conditional(c) => {
for branch in &c.branches {
walk_entries_inner(&branch.body, f, depth + 1);
}
if let Some(els) = &c.otherwise {
walk_entries_inner(els, f, depth + 1);
}
}
_ => {}
}
}
}
pub fn neighbour_is_comment(items: &[FilesContent<Span>], i: usize) -> bool {
let before = i.checked_sub(1).map(|j| &items[j]);
let after = items.get(i + 1);
before.is_some_and(|x| matches!(x, FilesContent::Comment(_)))
|| after.is_some_and(|x| matches!(x, FilesContent::Comment(_)))
}
pub fn pkg_name_for(main_name: Option<&str>, subpkg: Option<&rpm_spec::ast::SubpkgRef>) -> String {
resolve_subpkg_name(main_name, subpkg)
.or_else(|| main_name.map(str::to_owned))
.unwrap_or_default()
}
pub fn resolve_subpkg_name(
main_name: Option<&str>,
subpkg: Option<&rpm_spec::ast::SubpkgRef>,
) -> Option<String> {
use rpm_spec::ast::SubpkgRef;
let r = subpkg?;
match r {
SubpkgRef::Relative(t) => {
let suffix = t.literal_str()?.trim();
let main = main_name?;
if suffix.is_empty() {
return None;
}
Some(format!("{main}-{suffix}"))
}
SubpkgRef::Absolute(t) => {
let n = t.literal_str()?.trim();
if n.is_empty() {
None
} else {
Some(n.to_owned())
}
}
_ => None,
}
}