1use std::fs;
6
7use tern_sdk::{
8 PrintOptions,
9 ui::{View, html},
10};
11
12const CSS: &str = ".ls{border-spacing:0} .ls th{text-align:left;padding:0 18px 3px \
14 0;color:var(--sf-c-muted);font-weight:500} .ls td{padding:1px 18px 1px 0} .ls \
15 .size{text-align:right} .ls .dir{color:var(--sf-c-accent)}";
16
17fn human(bytes: u64) -> String {
19 const UNITS: [&str; 4] = ["K", "M", "G", "T"];
20 if bytes < 1024 {
21 return bytes.to_string();
22 }
23 let mut size = bytes as f64 / 1024.0;
24 let mut unit = 0;
25 while size >= 1024.0 && unit < UNITS.len() - 1 {
26 size /= 1024.0;
27 unit += 1;
28 }
29 if size < 10.0 {
30 format!("{size:.1}{}", UNITS[unit])
31 } else {
32 format!("{size:.0}{}", UNITS[unit])
33 }
34}
35
36struct Entry {
38 name: String,
40 dir: bool,
42 size: u64,
44}
45
46fn view(entries: &[Entry]) -> View {
48 let head = html::tr()
49 .child(html::th("Name"))
50 .child(html::th("Size").class("size"));
51 let rows = entries.iter().map(|e| {
52 let name = if e.dir {
53 format!("{}/", e.name)
54 } else {
55 e.name.clone()
56 };
57 let size = if e.dir { "-".to_owned() } else { human(e.size) };
58 html::tr()
59 .child(html::td(name).class(if e.dir { "dir" } else { "file" }))
60 .child(html::td(size).class("size"))
61 });
62 View::new().main([html::table()
63 .class("ls")
64 .child(html::thead().child(head))
65 .child(html::tbody().children(rows))])
66}
67
68#[cfg(test)]
69mod tests {
70 use super::*;
71
72 #[test]
73 fn a_file_named_head_does_not_collide_with_the_header() {
74 let view = view(&[Entry { name: "head".into(), dir: false, size: 12 }]);
75 assert!(tern_sdk::reconcile::Doc::from_view(view.clone()).is_ok());
76 assert_eq!(tern_sdk::plain(view, 80), "Name Size\nhead 12\n");
77 }
78}
79
80fn main() -> Result<(), tern_sdk::Error> {
81 let mut entries: Vec<Entry> = fs::read_dir(".")?
82 .filter_map(Result::ok)
83 .filter_map(|e| {
84 let meta = e.metadata().ok()?;
85 Some(Entry {
86 name: e.file_name().to_string_lossy().into_owned(),
87 dir: meta.is_dir(),
88 size: meta.len(),
89 })
90 })
91 .collect();
92 entries.sort_by(|a, b| b.dir.cmp(&a.dir).then_with(|| a.name.cmp(&b.name)));
93 tern_sdk::print(view(&entries), PrintOptions::new().css(CSS))
94}