///|
pub(all) enum FileCategory {
SourceCode
Documents
Images
Audio
Video
Archives
Data
Executables
Fonts
Logs
Temporary
OtherFiles
} derive(Eq, Debug, ToJson, FromJson)
///|
fn string_in(value : String, choices : Array[String]) -> Bool {
for choice in choices {
if value == choice {
return true
}
}
false
}
///|
pub fn file_category(entry : Entry) -> FileCategory {
let extension = entry.extension()
let name = entry.name().to_lower()
if string_in(extension, [
"mbt", "c", "h", "cc", "cpp", "hpp", "rs", "go", "py", "js", "jsx", "ts", "tsx",
"java", "kt", "swift", "rb", "php", "cs", "fs", "scala", "lua", "sh", "bash",
"zsh", "ps1", "sql", "html", "css", "scss", "vue",
]) ||
string_in(name, ["makefile", "dockerfile", "justfile"]) {
SourceCode
} else if string_in(extension, [
"md", "txt", "rst", "pdf", "doc", "docx", "odt", "rtf", "ppt", "pptx", "odp",
"xls", "xlsx", "ods", "epub", "tex",
]) {
Documents
} else if string_in(extension, [
"png", "jpg", "jpeg", "gif", "webp", "svg", "bmp", "ico", "tif", "tiff", "avif",
"heic", "raw",
]) {
Images
} else if string_in(extension, [
"mp3", "wav", "flac", "aac", "ogg", "m4a", "opus", "wma",
]) {
Audio
} else if string_in(extension, [
"mp4", "mkv", "mov", "avi", "webm", "wmv", "flv", "m4v", "mpeg", "mpg",
]) {
Video
} else if string_in(extension, [
"zip", "7z", "rar", "tar", "gz", "bz2", "xz", "zst", "tgz", "cab",
]) {
Archives
} else if string_in(extension, [
"json", "jsonl", "csv", "tsv", "xml", "yaml", "yml", "toml", "ini", "db", "sqlite",
"sqlite3", "parquet", "arrow", "pkl",
]) {
Data
} else if string_in(extension, [
"exe", "dll", "so", "dylib", "bin", "app", "msi", "apk", "wasm", "o", "obj",
"a", "lib", "class", "jar",
]) {
Executables
} else if string_in(extension, ["ttf", "otf", "woff", "woff2", "eot"]) {
Fonts
} else if string_in(extension, ["log", "trace", "out"]) {
Logs
} else if string_in(extension, ["tmp", "temp", "bak", "swp"]) ||
name.has_suffix("~") {
Temporary
} else {
OtherFiles
}
}
///|
pub fn file_category_name(category : FileCategory) -> String {
match category {
SourceCode => "Source code"
Documents => "Documents"
Images => "Images"
Audio => "Audio"
Video => "Video"
Archives => "Archives"
Data => "Structured data"
Executables => "Executables and build artifacts"
Fonts => "Fonts"
Logs => "Logs"
Temporary => "Temporary files"
OtherFiles => "Other"
}
}
///|
pub fn file_category_key(category : FileCategory) -> String {
match category {
SourceCode => "source-code"
Documents => "documents"
Images => "images"
Audio => "audio"
Video => "video"
Archives => "archives"
Data => "data"
Executables => "executables"
Fonts => "fonts"
Logs => "logs"
Temporary => "temporary"
OtherFiles => "other"
}
}
///|
pub fn file_category_from_key(key : String) -> FileCategory raise {
match key.to_lower() {
"source" | "source-code" | "code" => SourceCode
"document" | "documents" => Documents
"image" | "images" => Images
"audio" => Audio
"video" => Video
"archive" | "archives" => Archives
"data" => Data
"executable" | "executables" | "binary" => Executables
"font" | "fonts" => Fonts
"log" | "logs" => Logs
"temporary" | "temp" => Temporary
"other" => OtherFiles
value => raise Failure("Unknown file category: " + value)
}
}
///|
pub fn category_stats(snapshot : Snapshot) -> Array[CategoryStat] {
let sizes : Map[String, Int64] = Map([])
let counts : Map[String, Int] = Map([])
for entry in snapshot.entries {
if entry.is_file() {
let name = file_category_name(file_category(entry))
map_add_size(sizes, name, entry.size)
map_add_count(counts, name, 1)
}
}
let result : Array[CategoryStat] = []
for name, size in sizes {
result.push(CategoryStat::{ name, size, files: counts[name], })
}
result.sort_by(fn(a, b) {
if a.size > b.size {
-1
} else if a.size < b.size {
1
} else if a.name < b.name {
-1
} else {
1
}
})
result
}
///|
pub fn files_in_category(
snapshot : Snapshot,
category : FileCategory,
) -> Array[Entry] {
let result : Array[Entry] = []
for entry in snapshot.entries {
if entry.is_file() && file_category(entry) == category {
result.push(entry)
}
}
result.sort_by(fn(a, b) {
if a.size > b.size {
-1
} else if a.size < b.size {
1
} else if a.path < b.path {
-1
} else {
1
}
})
result
}