use std::fmt::Write as _;
use crate::registry_core::declaration::source_file_matches;
use crate::{CallTrace, Registry};
pub fn render_frame(registry: &Registry, tick: u64, event: &str) -> String {
format!(
"REGISTRATION DEBUG tick={tick}\nTREE\n{}\nEVENT\n{event}\n",
registry.dump()
)
}
pub fn render_call_report(registry: &Registry, query: Option<&str>) -> String {
let trace = CallTrace::new();
render_call_report_for_trace(registry, &trace, query)
}
pub fn render_call_report_for_trace(
registry: &Registry,
trace: &CallTrace,
query: Option<&str>,
) -> String {
let paths = matching_call_paths(registry, trace, query);
let heading = query.map_or_else(
|| "call tree:".to_owned(),
|query| format!("call search `{query}`: {} matching path(s)", paths.len()),
);
if paths.is_empty() {
let registrations = query
.map(|query| matching_registrations(registry, query))
.unwrap_or_default();
if registrations.is_empty() {
return format!("{heading}\nno matching function, method, step, or declaration file");
}
let mut output = format!("{heading}\nregistered faces (no call event recorded yet):");
for info in registrations {
let path = registry
.path_for(info.id)
.unwrap_or_else(|| "<unknown>".to_owned());
let function = query
.filter(|query| {
info.runtime_checks.iter().any(|check| {
check
.name()
.to_ascii_lowercase()
.contains(&query.to_ascii_lowercase())
})
})
.and_then(|query| {
info.runtime_checks
.iter()
.find(|check| {
check
.name()
.to_ascii_lowercase()
.contains(&query.to_ascii_lowercase())
})
.map(|check| check.name())
})
.unwrap_or(&info.source.function);
writeln!(
output,
" `-- {}::{} kind={} declared-at={}",
path,
function,
info.kind,
info.source.describe(),
)
.unwrap();
}
return output;
}
let mut output = heading;
for path in paths {
output.push('\n');
for (depth, call) in path.iter().enumerate() {
let prefix = if depth + 1 == path.len() {
"`--"
} else {
"|--"
};
let details = registry
.find(call.node)
.map(|info| {
let path = registry
.path_for(call.node)
.unwrap_or_else(|| "<unknown>".to_owned());
let call_source = call
.source
.map(|source| format!(" call-at={source}"))
.unwrap_or_default();
format!(
"{} {}::{}#{} declared-at={}{call_source}",
call.node,
path,
call.function,
call.frame_id,
info.source.describe(),
)
})
.unwrap_or_else(|| format!("{} {}", call.node, call.function));
writeln!(output, " {}{} {}", " ".repeat(depth), prefix, details).unwrap();
}
}
output
}
fn matching_registrations<'a>(
registry: &'a Registry,
query: &str,
) -> Vec<&'a crate::RegistrationSnapshot> {
let query = query.to_ascii_lowercase();
registry.find_where(|info| registration_matches(registry, info, &query))
}
fn registration_matches(
registry: &Registry,
info: &crate::RegistrationSnapshot,
query: &str,
) -> bool {
let text_matches = [
info.kind.as_str(),
info.preset.as_str(),
info.parts.as_str(),
info.source.file.as_str(),
info.source.function.as_str(),
info.name.zh.as_str(),
info.name.en.as_str(),
info.summary.zh.as_str(),
info.summary.en.as_str(),
info.registry_name.as_str(),
]
.into_iter()
.any(|value| value.to_ascii_lowercase().contains(query));
text_matches
|| info.id.to_string().contains(query)
|| registry
.path_for(info.id)
.is_some_and(|path| path.to_ascii_lowercase().contains(query))
|| info
.exports
.iter()
.any(|export| export.to_ascii_lowercase().contains(query))
|| info.requires.iter().any(|requirement| {
requirement.capability.to_ascii_lowercase().contains(query)
|| requirement.provider.to_ascii_lowercase().contains(query)
})
|| info
.provides
.iter()
.any(|provide| provide.to_ascii_lowercase().contains(query))
|| info
.runtime_checks
.iter()
.any(|check| check.name().to_ascii_lowercase().contains(query))
}
fn matching_call_paths(
registry: &Registry,
trace: &CallTrace,
query: Option<&str>,
) -> Vec<Vec<crate::CallSite>> {
let query = query.map(str::trim).filter(|query| !query.is_empty());
let matches = trace
.frame_ids()
.filter_map(|frame_id| trace.path_for_frame(frame_id))
.filter(|path| {
query.is_none_or(|query| {
let query = query.to_ascii_lowercase();
path.iter().any(|call| {
call.node.to_string().contains(&query)
|| call.function.to_ascii_lowercase().contains(&query)
|| call
.source
.is_some_and(|source| source_file_matches(source.file, &query))
|| registry
.find(call.node)
.is_some_and(|info| registration_matches(registry, info, &query))
})
})
})
.collect::<Vec<_>>();
matches
.iter()
.filter(|path| {
!matches
.iter()
.any(|other| other.len() > path.len() && other.starts_with(path.as_slice()))
})
.cloned()
.collect()
}