Skip to main content

table/
table.rs

1//! `print`: the current directory as a native `table` (sizes end-aligned,
2//! mono), with no stylesheet. In Tern it stays in the scrollback as a native
3//! table; anywhere else it prints as aligned plain text.
4
5use std::fs;
6
7use tern_sdk::{
8	PrintOptions,
9	ui::{self, Column, TableRow, TextAlign, TextCell, View},
10};
11
12/// A size as `312`, `2.1K`, `14K`, `3.4M`.
13fn human(bytes: u64) -> String {
14	const UNITS: [&str; 4] = ["K", "M", "G", "T"];
15	if bytes < 1024 {
16		return bytes.to_string();
17	}
18	let mut size = bytes as f64 / 1024.0;
19	let mut unit = 0;
20	while size >= 1024.0 && unit < UNITS.len() - 1 {
21		size /= 1024.0;
22		unit += 1;
23	}
24	if size < 10.0 {
25		format!("{size:.1}{}", UNITS[unit])
26	} else {
27		format!("{size:.0}{}", UNITS[unit])
28	}
29}
30
31/// One entry: its name, whether it is a directory, and its size.
32struct Entry {
33	/// The file name.
34	name: String,
35	/// A directory.
36	dir:  bool,
37	/// The size in bytes.
38	size: u64,
39}
40
41/// The directory listing as a `table`: folders first with a folder icon,
42/// sizes end-aligned in the mono face.
43fn view(entries: &[Entry]) -> View {
44	let rows = entries.iter().map(|e| {
45		let (name, icon) = if e.dir {
46			(format!("{}/", e.name), "folder")
47		} else {
48			(e.name.clone(), "file")
49		};
50		let size = if e.dir { "-".to_owned() } else { human(e.size) };
51		TableRow::new(e.name.clone())
52			.cell("name", TextCell::new(name).icon(icon))
53			.cell("size", size)
54	});
55	View::new().main([ui::table()
56		.col(Column::new("name", "Name").grow(1.0))
57		.col(Column::new("size", "Size").align(TextAlign::End).mono(true))
58		.rows(rows.collect::<Vec<_>>())])
59}
60
61#[cfg(test)]
62mod tests {
63	use super::*;
64
65	#[test]
66	fn a_file_named_head_does_not_collide_with_the_header() {
67		let view = view(&[Entry { name: "head".into(), dir: false, size: 12 }]);
68		assert!(tern_sdk::reconcile::Doc::from_view(view.clone()).is_ok());
69		assert_eq!(tern_sdk::plain(view, 80), "Name  Size\nhead    12\n");
70	}
71}
72
73fn main() -> Result<(), tern_sdk::Error> {
74	let mut entries: Vec<Entry> = fs::read_dir(".")?
75		.filter_map(Result::ok)
76		.filter_map(|e| {
77			let meta = e.metadata().ok()?;
78			Some(Entry {
79				name: e.file_name().to_string_lossy().into_owned(),
80				dir:  meta.is_dir(),
81				size: meta.len(),
82			})
83		})
84		.collect();
85	entries.sort_by(|a, b| b.dir.cmp(&a.dir).then_with(|| a.name.cmp(&b.name)));
86	tern_sdk::print(view(&entries), PrintOptions::new())
87}