From 0f0bee680250e98859c9bbc7f91e44ff4afd9f4e Mon Sep 17 00:00:00 2001 From: erius Date: Wed, 24 Apr 2024 03:45:48 +0300 Subject: [PATCH] Implemented basic rbls functionality with formatted output and flags -a and -A Added crate "console" for access to the terminals properties --- Cargo.lock | 38 +++++++++++++++++++++ Cargo.toml | 1 + src/bin/rbls.rs | 89 ++++++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 119 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 93070be..a62628a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -96,12 +96,43 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "console" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "unicode-width", + "windows-sys", +] + +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + [[package]] name = "proc-macro2" version = "1.0.81" @@ -125,6 +156,7 @@ name = "rbutils" version = "0.1.0" dependencies = [ "clap", + "console", ] [[package]] @@ -150,6 +182,12 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + [[package]] name = "utf8parse" version = "0.2.1" diff --git a/Cargo.toml b/Cargo.toml index 5cb6b3b..04d2c76 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,3 +7,4 @@ edition = "2021" [dependencies] clap = { version = "4.5.4", features = ["derive"] } +console = "0.15.8" diff --git a/src/bin/rbls.rs b/src/bin/rbls.rs index 0ee2d86..a321806 100644 --- a/src/bin/rbls.rs +++ b/src/bin/rbls.rs @@ -1,19 +1,90 @@ use clap::Parser; -use std::{ffi::OsString, fs, io}; +use console::Term; +use std::io::{self, Write}; +use std::fs::{self, DirEntry, Metadata}; -fn main() { +fn main() -> Result<(), io::Error> { let cli = Cli::parse(); - let names = get_files(&cli.files[0]); - let stdout = std::io::stdout().lock(); + let file_infos = get_files_metadata(&cli.files[0])?; + let file_names = format_file_names(file_infos, cli.all, cli.almost_all); + let term = Term::stdout(); + let output = if term.is_term() { format_term(file_names, term.size().1) } + else { file_names.join(" ") }; + let mut stdout = io::stdout().lock(); + write!(stdout, "{}", output)?; + writeln!(stdout)?; + return Ok(()); } -fn get_files(file_name: &str) -> Result, io::Error> { - let contents = fs::read_dir(file_name)?; - let mut names = vec![]; +fn get_files_metadata(file_name: &str) -> Result, io::Error> { + let Ok(contents) = fs::read_dir(file_name) + else { + let file_info = FileInfo::new(file_name.to_string(), fs::metadata(file_name)?); + return Ok(vec![file_info]); + }; + let mut file_infos = vec![ + FileInfo::new(".".to_string(), fs::metadata(".")?), + FileInfo::new("..".to_string(), fs::metadata("..")?), + ]; for rd in contents { - names.push(rd?.file_name()); + file_infos.push(FileInfo::try_from(rd)?); + } + return Ok(file_infos); +} + +fn format_file_names(file_infos: Vec, show_all: bool, show_almost_all: bool) -> Vec { + return file_infos.into_iter() + .filter(|file_info| { + if show_all { return true; } + if show_almost_all { + return file_info.name != "." && file_info.name != ".."; + } + return !file_info.name.starts_with("."); + }) + .map(|file_info| { + let mut file_name = file_info.name; + if file_info.metadata.is_dir() { file_name.push('/') } + return file_name; + }) + .collect(); +} + +fn format_term(mut file_names: Vec, term_width: u16) -> String { + let max_file_name_len = file_names.iter().map(|n| n.len()).max().unwrap(); + let elem_width = max_file_name_len + 2; + let row_elems_amount = (term_width as usize / elem_width).max(1); + for (i, name) in file_names.iter_mut().enumerate() { + name.push_str(&" ".repeat(max_file_name_len - name.len() + 2)); + if (i + 1) % row_elems_amount == 0 { name.push('\n'); } + } + return file_names.join("").trim_end().to_string(); +} + +struct FileInfo { + name: String, metadata: Metadata +} + +impl FileInfo { + fn new(name: String, metadata: Metadata) -> Self { Self { name, metadata } } +} + +impl TryFrom for FileInfo { + type Error = io::Error; + + fn try_from(de: DirEntry) -> Result { + let name = de.file_name().into_string().unwrap(); + Ok(Self { name, metadata: de.metadata()? }) + } +} + +impl TryFrom> for FileInfo { + type Error = io::Error; + + fn try_from(de: Result) -> Result { + let de = de?; + let name = de.file_name().into_string().unwrap(); + Ok(Self { name, metadata: de.metadata()? }) } - return Ok(names); } #[derive(Parser)]