use super::rules::RuleSet;
use crate::layout::{LaidOut, PlacedNode};
use crate::resolve::{AttrMap, ResolvedValue, VarTable};
use std::collections::BTreeSet;
pub fn referenced(laid: &LaidOut, ruleset: &RuleSet) -> BTreeSet<String> {
let mut names = BTreeSet::new();
for rule in &ruleset.rules {
for (_, value) in &rule.props {
scan_css(value, &mut names);
}
}
for n in &laid.nodes {
walk_node(n, &mut names);
}
for w in &laid.wires {
walk_attrs(&w.attrs, &mut names);
for t in &w.texts {
walk_attrs(&t.attrs, &mut names);
}
}
if !laid.airwires.is_empty() {
names.insert("airwire".to_string());
}
for (_, attrs) in &laid.sheet.class_rules {
walk_attrs(attrs, &mut names);
}
walk_attrs(&laid.sheet.wire_defaults, &mut names);
walk_attrs(&laid.sheet.root_text, &mut names);
if let Some(fill) = &laid.canvas_fill {
collect_live(fill, &mut names);
}
for g in &laid.gradients {
for stop in &g.stops {
collect_live(stop, &mut names);
}
}
close_over_vars(&mut names, &laid.vars);
names
}
fn walk_node(n: &PlacedNode, names: &mut BTreeSet<String>) {
walk_attrs(&n.attrs, names);
for c in &n.children {
walk_node(c, names);
}
}
fn walk_attrs(attrs: &AttrMap, names: &mut BTreeSet<String>) {
for value in attrs.map.values() {
collect_live(value, names);
}
}
fn collect_live(value: &ResolvedValue, names: &mut BTreeSet<String>) {
match value {
ResolvedValue::LiveVar { name, raw: false } => {
names.insert(name.clone());
}
ResolvedValue::Tuple(items) | ResolvedValue::List(items) => {
items.iter().for_each(|v| collect_live(v, names));
}
ResolvedValue::Call(c) => c.args.iter().for_each(|v| collect_live(v, names)),
_ => {}
}
}
fn scan_css(s: &str, names: &mut BTreeSet<String>) {
const PREFIX: &str = "var(--lini-";
let bytes = s.as_bytes();
let mut i = 0;
while let Some(off) = s[i..].find(PREFIX) {
let start = i + off + PREFIX.len();
let mut end = start;
while end < bytes.len() && (bytes[end].is_ascii_alphanumeric() || bytes[end] == b'-') {
end += 1;
}
if end > start {
names.insert(s[start..end].to_string());
}
i = end.max(start + 1);
}
}
fn close_over_vars(names: &mut BTreeSet<String>, vars: &VarTable) {
let mut frontier: Vec<String> = names.iter().cloned().collect();
while let Some(name) = frontier.pop() {
if let Some(value) = vars.get(&name) {
let mut found = BTreeSet::new();
collect_live(value, &mut found);
for f in found {
if names.insert(f.clone()) {
frontier.push(f);
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn used(src: &str) -> BTreeSet<String> {
let toks = crate::lexer::lex(src).expect("lex");
let file = crate::syntax::parser::parse(&toks).expect("parse");
let lowered = crate::desugar::desugar(&file).expect("desugar");
let program = crate::resolve::resolve_with_theme(&lowered, &[]).expect("resolve");
let laid = crate::layout::layout(&program).expect("layout");
let ruleset = super::super::rules::build(&laid, &crate::Options::default());
referenced(&laid, &ruleset)
}
#[test]
fn plain_diagram_keeps_core_roles_not_the_palette() {
let names = used("x |box|\n");
assert!(names.contains("fill"), "{names:?}");
assert!(names.contains("stroke"), "{names:?}");
assert!(names.contains("bg"), "{names:?}");
assert!(!names.contains("teal"), "{names:?}");
assert!(!names.contains("rose"), "{names:?}");
}
#[test]
fn a_used_hue_is_kept_others_are_not() {
let names = used("x |box| { fill: --teal }\n");
assert!(names.contains("teal"), "{names:?}");
assert!(!names.contains("rose"), "{names:?}");
}
#[test]
fn text_color_pulls_in_fg_transitively() {
let names = used("x |box|\n");
assert!(names.contains("text-color"), "{names:?}");
assert!(names.contains("fg"), "{names:?}");
}
}