code-moniker 0.4.0

Standalone CLI / linter for the code-moniker symbol graph: file and directory probes, project-wide architecture rules.
Documentation
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(in crate::ui) enum FitMode {
	Middle,
	Tail,
}

#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(in crate::ui) enum Align {
	Left,
	Right,
}

#[derive(Copy, Clone, Debug)]
pub(in crate::ui) struct Column {
	pub(in crate::ui) title: &'static str,
	pub(in crate::ui) width: usize,
	pub(in crate::ui) align: Align,
}

impl Column {
	pub(in crate::ui) const fn left(title: &'static str, width: usize) -> Self {
		Self {
			title,
			width,
			align: Align::Left,
		}
	}

	pub(in crate::ui) const fn right(title: &'static str, width: usize) -> Self {
		Self {
			title,
			width,
			align: Align::Right,
		}
	}
}

pub(in crate::ui) fn table_width(columns: &[Column], max_width: usize) -> usize {
	let widths = fitted_widths(columns, max_width);
	widths.iter().sum::<usize>() + gap_width(columns)
}

pub(in crate::ui) fn fitted_widths(columns: &[Column], max_width: usize) -> Vec<usize> {
	if columns.is_empty() {
		return Vec::new();
	}
	let gaps = gap_width(columns);
	let available = max_width.saturating_sub(gaps);
	let requested: Vec<_> = columns.iter().map(|column| column.width).collect();
	if requested.iter().sum::<usize>() <= available {
		return requested;
	}
	let mut widths = vec![0; columns.len()];
	let mut remaining = available;
	while remaining > 0
		&& widths
			.iter()
			.zip(&requested)
			.any(|(width, max)| width < max)
	{
		for (width, max) in widths.iter_mut().zip(&requested) {
			if remaining == 0 {
				break;
			}
			if *width < *max {
				*width += 1;
				remaining -= 1;
			}
		}
	}
	widths
}

fn gap_width(columns: &[Column]) -> usize {
	columns.len().saturating_sub(1) * 2
}

pub(in crate::ui) fn format_cell(value: &str, width: usize, align: Align) -> String {
	let value = fit_text(value, width, FitMode::Tail);
	match align {
		Align::Left => format!("{value:<width$}"),
		Align::Right => format!("{value:>width$}"),
	}
}

pub(in crate::ui) fn fit_text(value: &str, width: usize, mode: FitMode) -> String {
	if visible_len(value) <= width {
		return value.to_string();
	}
	match mode {
		FitMode::Middle => fit_middle(value, width),
		FitMode::Tail => fit_tail(value, width),
	}
}

fn fit_middle(value: &str, width: usize) -> String {
	if width == 0 {
		return String::new();
	}
	if width <= 3 {
		return ".".repeat(width);
	}
	let available = width - 3;
	let left = available / 2;
	let right = available - left;
	format!("{}...{}", take_start(value, left), take_end(value, right))
}

fn fit_tail(value: &str, width: usize) -> String {
	if width == 0 {
		return String::new();
	}
	if width <= 3 {
		return ".".repeat(width);
	}
	format!("...{}", take_end(value, width - 3))
}

fn take_start(value: &str, count: usize) -> String {
	value.chars().take(count).collect()
}

fn take_end(value: &str, count: usize) -> String {
	let chars: Vec<_> = value.chars().collect();
	chars[chars.len().saturating_sub(count)..].iter().collect()
}

pub(in crate::ui) fn visible_len(value: &str) -> usize {
	value.chars().count()
}