use crate::{Budget, BudgetKind, Budgets};
pub const DEFAULT_BYTES_PER_INPUT: usize = 500;
pub const LINE_ONLY_FREE_PREFIX_GRAPHEMES: usize = 40;
#[derive(Debug, Copy, Clone)]
pub struct EffectiveBudgets {
pub budgets: Budgets,
pub per_file_for_priority: usize,
pub line_only: bool,
}
#[allow(
clippy::cognitive_complexity,
reason = "Keeps budget roll-up rules in one place; splitting would scatter the defaults."
)]
pub fn compute_effective_budgets(
per_slot: Option<Budget>,
explicit_global: Option<Budget>,
input_count: usize,
default_per_input: usize,
) -> EffectiveBudgets {
let mut per_slot = per_slot;
let mut global = explicit_global;
if global.is_none() {
match per_slot {
Some(Budget {
kind: BudgetKind::Bytes,
cap,
}) => {
global = Some(Budget {
kind: BudgetKind::Bytes,
cap: cap.saturating_mul(input_count),
});
}
Some(Budget {
kind: BudgetKind::Chars,
cap,
}) => {
global = Some(Budget {
kind: BudgetKind::Chars,
cap: cap.saturating_mul(input_count),
});
}
Some(Budget {
kind: BudgetKind::Lines,
..
}) => {}
None => {
per_slot = Some(Budget {
kind: BudgetKind::Bytes,
cap: default_per_input,
});
global = Some(Budget {
kind: BudgetKind::Bytes,
cap: default_per_input.saturating_mul(input_count),
});
}
}
}
let budgets = Budgets { global, per_slot };
let has_lines = matches!(
budgets.global,
Some(Budget {
kind: BudgetKind::Lines,
..
})
) || matches!(
budgets.per_slot,
Some(Budget {
kind: BudgetKind::Lines,
..
})
);
let has_bytes_or_chars = matches!(
budgets.global,
Some(Budget {
kind: BudgetKind::Bytes | BudgetKind::Chars,
..
})
) || matches!(
budgets.per_slot,
Some(Budget {
kind: BudgetKind::Bytes | BudgetKind::Chars,
..
})
);
let line_only = has_lines && !has_bytes_or_chars;
let per_file_for_priority =
priority_cap(&budgets, input_count, default_per_input, line_only);
EffectiveBudgets {
budgets,
per_file_for_priority,
line_only,
}
}
fn priority_cap(
budgets: &Budgets,
input_count: usize,
default_per_input: usize,
line_only: bool,
) -> usize {
if line_only {
return usize::MAX;
}
let per_slot_cap = budgets
.per_slot
.and_then(|b| cap_for_priority(b, true, input_count));
let global_cap = budgets
.global
.and_then(|b| cap_for_priority(b, false, input_count));
per_slot_cap
.into_iter()
.chain(global_cap)
.min()
.unwrap_or(default_per_input)
}
fn cap_for_priority(
budget: Budget,
is_per_slot: bool,
input_count: usize,
) -> Option<usize> {
match budget.kind {
BudgetKind::Bytes | BudgetKind::Chars => {
if is_per_slot {
Some(budget.cap)
} else {
Some((budget.cap / input_count.max(1)).max(1))
}
}
BudgetKind::Lines => None,
}
}
pub fn render_config_for_budgets(
mut cfg: crate::RenderConfig,
effective: &EffectiveBudgets,
) -> crate::RenderConfig {
if effective.line_only {
cfg.string_free_prefix_graphemes =
Some(LINE_ONLY_FREE_PREFIX_GRAPHEMES);
}
cfg
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn per_file_priority_prefers_per_slot_cap_when_global_is_lines() {
let effective = compute_effective_budgets(
Some(Budget {
kind: BudgetKind::Bytes,
cap: 1024,
}),
Some(Budget {
kind: BudgetKind::Lines,
cap: 5,
}),
1,
DEFAULT_BYTES_PER_INPUT,
);
assert_eq!(
effective.per_file_for_priority, 1024,
"per-file priority tuning should respect per-slot byte caps even when the global budget is in lines"
);
}
}