const DIG_MIN = -12, DIG_MAX = 6;
function clampDig(z) { return Math.max(DIG_MIN, Math.min(DIG_MAX, (z | 0))); }
window.clampDig = clampDig;
function relPathOf(id) { return String(id || '').replace(/^\{[^}]+\}\//, ''); }
const _crateRootCache = new Map(); function crateRoots(level) {
if (_crateRootCache.has(level)) return _crateRootCache.get(level);
const gk = levelUi(level).grouping?.key;
const byCrate = new Map();
if (gk) {
for (const n of (unionGraph(level).nodes || [])) {
if (isExternalNode(n, level)) continue;
const crate = n[gk];
if (crate == null || crate === '') continue;
const dirs = relPathOf(n.id).split('/').slice(0, -1); const arr = byCrate.get(String(crate));
if (arr) arr.push(dirs); else byCrate.set(String(crate), [dirs]);
}
}
const roots = new Map();
for (const [crate, list] of byCrate) {
let prefix = list[0].slice();
for (let k = 1; k < list.length; k++) {
const segs = list[k];
let i = 0;
while (i < prefix.length && i < segs.length && prefix[i] === segs[i]) i++;
prefix = prefix.slice(0, i);
}
roots.set(crate, prefix);
}
_crateRootCache.set(level, roots);
return roots;
}
const _crateDirsCache = new Map(); const SRC_DIRS = new Set(['src', 'tests', 'benches', 'lib', 'bin']);
function crateDirs(level) {
if (_crateDirsCache.has(level)) return _crateDirsCache.get(level);
const dirOf = new Map();
let maxDepth = 0;
for (const [crate, segs0] of crateRoots(level)) {
const segs = segs0.slice();
while (segs.length && SRC_DIRS.has(segs[segs.length - 1])) segs.pop();
dirOf.set(crate, segs);
if (segs.length > maxDepth) maxDepth = segs.length;
}
const res = { dirOf, maxDepth };
_crateDirsCache.set(level, res);
return res;
}
function maxCrateDepth(level) { return crateDirs(level).maxDepth; }
window.maxCrateDepth = maxCrateDepth;
function clearGroupingCache() { _crateRootCache.clear(); _crateDirsCache.clear(); }
window.clearGroupingCache = clearGroupingCache;
function groupKeyAtDig(level, n, dig) {
if (isExternalNode(n, level))
return (nodeKindSpec(level, n.kind).plural || 'external').toLowerCase();
const d = dig | 0;
const gk = levelUi(level).grouping?.key;
const crate = gk ? n[gk] : null;
const dirs = relPathOf(n.id).split('/').slice(0, -1);
if (crate == null || crate === '') {
const depth = dirs.length + d;
const keep = dirs.slice(0, Math.max(0, depth));
return keep.length ? keep.join('/') : '_root';
}
if (d >= 0) {
const root = crateRoots(level).get(String(crate)) || [];
const underCrate = dirs.slice(root.length);
return [String(crate), ...underCrate.slice(0, d)].join('/');
}
const { dirOf, maxDepth } = crateDirs(level);
const path = dirOf.get(String(crate)) || [];
const cap = maxDepth + d;
const keep = path.slice(0, Math.max(0, cap));
return keep.length ? keep.join('/') : '_root';
}
function groupCountAtDig(level, dig) {
const d = dig | 0;
if (d > DIG_MAX || d < -maxCrateDepth(level)) return null;
const keys = new Set();
for (const n of (unionGraph(level).nodes || [])) keys.add(groupKeyAtDig(level, n, d));
return keys.size;
}
window.groupCountAtDig = groupCountAtDig;
function grouperForDig(level, dig) {
return n => groupKeyAtDig(level, n, dig || 0);
}
window.grouperForDig = grouperForDig;
function groupLabel(level, key, dig) {
const d = dig | 0;
if (key === '_root') return '/'; if (d > 0) {
const cut = key.indexOf('/');
const crate = cut >= 0 ? key.slice(0, cut) : key;
const under = cut >= 0 ? key.slice(cut + 1).split('/') : [];
const root = crateRoots(level).get(crate) || [];
const crateD = crateDirs(level).dirOf.get(crate) || [];
const srcTail = root.slice(crateD.length); const full = [...crateD, ...srcTail, ...under]; return full.length ? '/' + full.join('/') : key;
}
if (d < 0) return key; return key.includes('/') ? key.slice(key.lastIndexOf('/') + 1) : key;
}
window.groupLabel = groupLabel;
function crateRelDir(level, n) {
const gk = levelUi(level).grouping?.key;
const segs = relPathOf(n.id).split('/').slice(0, -1); const crate = gk ? n[gk] : null;
if (crate == null || crate === '') return segs.length ? '/' + segs.join('/') : '/';
const cdir = crateDirs(level).dirOf.get(String(crate)) || [];
const rel = segs.slice(cdir.length);
return rel.length ? '/' + rel.join('/') : '/';
}
window.crateRelDir = crateRelDir;
function nodeFullDir(n) {
const segs = relPathOf(n.id).split('/').slice(0, -1); return segs.length ? '/' + segs.join('/') : '/';
}
window.nodeFullDir = nodeFullDir;
function aggCycleStatus(statuses) {
let b = false, c = false, both = false;
for (const s of statuses) {
if (s === 'both') both = true;
else if (s === 'baseline-only') b = true;
else if (s === 'current-only') c = true;
}
if (both || (b && c)) return 'both';
if (b) return 'baseline-only';
if (c) return 'current-only';
return 'none';
}