File tree
A folder's files, recursively, with sizes and dates: listings with limits, a folder's total size, and its largest and newest files.
Version 1.0.0 · 4 functions · 1 crate
Functions
What use file_tree.qwer gives a program, with the types Quillo sees. To take only some, or under other names: use file_tree, file_tree_size from file_tree (Quillo 0.6).
file_tree(dir: str, max_depth: int, hidden: bool) → listor an errorEverything under the folder dir, folders first-to-last in name order: { path, name, kind, size, modified, depth }. path is relative to dir; kind is file, dir or link (links are not followed); size in bytes; modified in seconds since 1970. max_depth 1 lists only dir itself, 0 goes all the way down; hidden false leaves out names starting with a dot. At most 100,000 entries.
file_tree_size(dir: str) → anyor an errorThe folder's totals, hidden files included: { files, dirs, bytes }.
file_tree_largest(dir: str, count: int) → listor an errorThe count largest files under the folder (up to 1,000), largest first: to find what fills a disk.
file_tree_newest(dir: str, count: int) → listor an errorThe count files changed last under the folder (up to 1,000), newest first: for "recent uploads" or a sync.
Get it
$ quillo install file_tree # file_tree.qwer into this folder$ quillo install file_tree --example # and its example, file_tree.qweOr with curl:
$ curl -O https://quillolang.org/plugins/files/file_tree.qwer$ curl -O https://quillolang.org/plugins/files/file_tree.qweThen, in your program, all of it or only what you call:
use file_tree.qweruse file_tree, file_tree_size from file_tree // Quillo 0.6: only theseIt uses this crate, which cargo fetches the first time the plugin is built:
walkdir = "2"Example
// A file manager's page for an uploads folder: the tree, its size, and the biggest files.//// quillo run file_tree.qwe then open http://localhost:8080/filesuse "file_tree.qwer"fn seed() { write_file("data/library/reports/2026-q3.csv", "region,total\nNorth,18250\nSouth,16410\n") write_file("data/library/reports/2026-q2.csv", "region,total\nNorth,17000\n") write_file("data/library/photos/shop.txt", repeat("a picture would be here ", 400)) write_file("data/library/.cache/x", "hidden") write_file("data/library/readme.md", "# Library\n")}route GET /files { seed() { tree: file_tree("data/library", 0, false)? | map((e) => { path: e.path, kind: e.kind, size: e.size }), size: file_tree_size("data/library")?, largest: file_tree_largest("data/library", 1)? | map((e) => e.path), }}route GET /top = file_tree("data/library", 1, false)? | map((e) => e.name)route GET / { seed() <html><body><ul> {for e in file_tree("data/library", 0, false)? => <li style={`margin-left: ${e.depth}em`}>{e.name} {e.kind == "file" ? `(${e.size} bytes)` : ""}</li>} </ul></body></html>}$ quillo run file_tree.qwe$ curl 'localhost:8080/files'$ curl 'localhost:8080/top'Source
//! A folder's files, recursively, with their sizes and dates: listings with limits, a folder's total size, and its largest and newest files.//! version: 1.0.0//!//! [dependencies]//! walkdir = "2"use serde_json::{json, Value};use std::time::UNIX_EPOCH;const MAX_ENTRIES: usize = 100_000;fn hidden(e: &walkdir::DirEntry) -> bool { e.depth() > 0 && e.file_name().to_str().map_or(false, |s| s.starts_with('.'))}// The folder's entries below it (not following links), hidden ones left out unless asked, at most 100,000.fn entries(dir: &str, max_depth: i64, show_hidden: bool) -> Result<Vec<walkdir::DirEntry>, String> { if !std::path::Path::new(dir).is_dir() { return Err(format!("{}: not a folder", dir)); } let depth = if max_depth <= 0 { usize::MAX } else { max_depth as usize }; let mut out = vec![]; for e in walkdir::WalkDir::new(dir).min_depth(1).max_depth(depth).follow_links(false).sort_by_file_name().into_iter().filter_entry(|e| show_hidden || !hidden(e)) { let Ok(e) = e else { continue }; out.push(e); if out.len() >= MAX_ENTRIES { break; } } Ok(out)}fn record(dir: &str, e: &walkdir::DirEntry) -> Value { let meta = e.metadata().ok(); let kind = if e.file_type().is_dir() { "dir" } else if e.file_type().is_symlink() { "link" } else { "file" }; let rel = e.path().strip_prefix(dir).unwrap_or(e.path()).to_string_lossy().replace('\\', "/"); json!({ "path": rel, "name": e.file_name().to_string_lossy(), "kind": kind, "size": if kind == "file" { meta.as_ref().map_or(0, |m| m.len()) } else { 0 }, "modified": meta.and_then(|m| m.modified().ok()).and_then(|t| t.duration_since(UNIX_EPOCH).ok()).map_or(0, |d| d.as_secs()), "depth": e.depth(), })}/// Everything under the folder `dir`, folders first-to-last in name order: `{ path, name, kind, size, modified, depth }`./// `path` is relative to `dir`; `kind` is file, dir or link (links are not followed); `size` in bytes; `modified` in seconds since 1970./// `max_depth` 1 lists only `dir` itself, 0 goes all the way down; `hidden` false leaves out names starting with a dot. At most 100,000 entries.pub fn file_tree(dir: &str, max_depth: i64, hidden: bool) -> Result<Vec<Value>, String> { Ok(entries(dir, max_depth, hidden)?.iter().map(|e| record(dir, e)).collect())}/// The folder's totals, hidden files included: `{ files, dirs, bytes }`.pub fn file_tree_size(dir: &str) -> Result<Value, String> { let (mut files, mut dirs, mut bytes) = (0u64, 0u64, 0u64); for e in entries(dir, 0, true)? { if e.file_type().is_dir() { dirs += 1; } else if e.file_type().is_file() { files += 1; bytes += e.metadata().map_or(0, |m| m.len()); } } Ok(json!({ "files": files, "dirs": dirs, "bytes": bytes }))}/// The `count` largest files under the folder (up to 1,000), largest first: to find what fills a disk.pub fn file_tree_largest(dir: &str, count: i64) -> Result<Vec<Value>, String> { let mut files: Vec<Value> = entries(dir, 0, true)?.iter().filter(|e| e.file_type().is_file()).map(|e| record(dir, e)).collect(); files.sort_by(|a, b| b["size"].as_u64().cmp(&a["size"].as_u64())); files.truncate(count.clamp(1, 1000) as usize); Ok(files)}/// The `count` files changed last under the folder (up to 1,000), newest first: for "recent uploads" or a sync.pub fn file_tree_newest(dir: &str, count: i64) -> Result<Vec<Value>, String> { let mut files: Vec<Value> = entries(dir, 0, false)?.iter().filter(|e| e.file_type().is_file()).map(|e| record(dir, e)).collect(); files.sort_by(|a, b| b["modified"].as_u64().cmp(&a["modified"].as_u64()).then(a["path"].as_str().cmp(&b["path"].as_str()))); files.truncate(count.clamp(1, 1000) as usize); Ok(files)}Related
disk_space.qwer · 1.0.0Free and total space of the disk a path is on, for health checks, refusing uploads before the disk fills, and admin pages.8 views · 8 downloadsGlob glob.qwer · 1.0.0Files found by glob patterns like docs/**/*.md, with patterns to leave out, and paths tested against patterns.7 views · 7 downloads3D models models_3d.qwer · 1.0.0STL and OBJ models read: triangles, size, volume, surface area and whether the mesh is closed, a 3D-print quote by weight, and OBJ turned into binary STL.6 views · 7 downloads7-Zip sevenz.qwer · 1.0.07-Zip archives listed, one file read, opened safely into a folder, and made from a folder.7 views · 8 downloadsBrotli brotli.qwer · 1.0.0Brotli compression of files and text: smaller than gzip for HTML, CSS and JavaScript served pre-compressed.7 views · 9 downloadsbzip2 bzip2.qwer · 1.0.0bzip2 compression of files and text in pure Rust, .bz2 dumps unpacked, and their first lines read without unpacking them all.6 views · 7 downloadsThread 0 posts
On File tree: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.