From 5b19719960f63e55cdc45f63ad9f99b749613fe1 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Mon, 12 Oct 2020 17:04:44 -0500 Subject: [PATCH 001/133] Ls: create ls utility --- Cargo.toml | 1 + ls/Cargo.toml | 19 +++++++++ ls/build.rs | 24 ++++++++++++ ls/src/cli.rs | 32 ++++++++++++++++ ls/src/main.rs | 102 +++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 178 insertions(+) create mode 100644 ls/Cargo.toml create mode 100644 ls/build.rs create mode 100644 ls/src/cli.rs create mode 100644 ls/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index 2e5df254..44e34d68 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ members = [ "id", "link", "logname", + "ls", "mkdir", "mkfifo", "mktemp", diff --git a/ls/Cargo.toml b/ls/Cargo.toml new file mode 100644 index 00000000..ce01350b --- /dev/null +++ b/ls/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "ls" +version = "0.1.0" +authors = ["Jeremy Jackson "] +license = "MPL-2.0-no-copyleft-exception" +build = "build.rs" +edition = "2018" +description = "List information about the FILEs (the current directory by default)." + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +clap = { version = "^2.33.0", features = ["wrap_help"] } +unix_mode = "0.1.1" +chrono = "0.4" + + +[build-dependencies] +clap = "^2.33.0" diff --git a/ls/build.rs b/ls/build.rs new file mode 100644 index 00000000..17f3863e --- /dev/null +++ b/ls/build.rs @@ -0,0 +1,24 @@ +use std::env; + +use clap::Shell; + +#[path = "src/cli.rs"] +mod cli; + +fn main() { + let mut app = cli::create_app(); + + let out_dir = match env::var("OUT_DIR") { + Ok(dir) => dir, + Err(err) => { + eprintln!("No OUT_DIR: {}", err); + return; + }, + }; + + app.gen_completions("ls", Shell::Zsh, out_dir.clone()); + app.gen_completions("ls", Shell::Fish, out_dir.clone()); + app.gen_completions("ls", Shell::Bash, out_dir.clone()); + app.gen_completions("ls", Shell::PowerShell, out_dir.clone()); + app.gen_completions("ls", Shell::Elvish, out_dir); +} diff --git a/ls/src/cli.rs b/ls/src/cli.rs new file mode 100644 index 00000000..402d5984 --- /dev/null +++ b/ls/src/cli.rs @@ -0,0 +1,32 @@ +use clap::{ + crate_authors, crate_description, crate_name, crate_version, App, AppSettings::ColoredHelp, Arg, +}; + +pub(crate) fn create_app<'a, 'b>() -> App<'a, 'b> { + App::new(crate_name!()) + .version(crate_version!()) + .author(crate_authors!()) + .about(crate_description!()) + .help_message("Display help information.") + .version_message("Display version information.") + .help_short("?") + .settings(&[ColoredHelp]) + .arg( + Arg::with_name("FILE") + .help("File(s) to list.") + .required(true) + .multiple(true) + .default_value(".") + ) + .arg( + Arg::with_name("list") + .help("Use a long listing format.") + .short("l") + ) + .arg( + Arg::with_name("all") + .help("Do not ignore entries starting with .") + .short("a") + .long("all") + ) +} diff --git a/ls/src/main.rs b/ls/src/main.rs new file mode 100644 index 00000000..57c644ce --- /dev/null +++ b/ls/src/main.rs @@ -0,0 +1,102 @@ +use std::{fs, process}; +use std::os::linux::fs::MetadataExt; +use std::os::unix::fs::PermissionsExt; + +extern crate chrono; + +use chrono::prelude::{DateTime, Utc}; + +mod cli; + +fn main() { + let matches = cli::create_app().get_matches(); + + let files = matches.values_of("FILE").unwrap(); + let list = matches.is_present("list"); + let all = matches.is_present("all"); + + let mut exit_code = 0; + + for file in files { + match fs::read_dir(file) { + Ok(dir) => { + let mut dir: Vec<_> = dir.map(|r| r.unwrap()).collect(); + + dir.sort_by_key(|dir| dir.path()); + + if list { + exit_code = print_list(dir, all); + } else { + exit_code = print_default(dir, all); + } + } + Err(err) => { + eprintln!("ls: cannot access '{}': {}", file, err); + exit_code = 1; + }, + } + } + + if exit_code != 0 { + process::exit(exit_code); + } +} + +fn print_default(dir: Vec, hidden: bool) -> i32 { + let exit_code = 1; + + for entry in dir { + let file_name = entry.file_name().into_string().unwrap(); + + if is_hidden(&file_name) && !hidden { + continue; + } + + print!("{} ", file_name); + } + println!(); + + exit_code +} + +fn print_list(dir: Vec, hidden: bool) -> i32 { + let mut exit_code = 1; + + for entry in dir { + match fs::metadata(entry.path()) { + Ok(meta_data) => { + let file_name = entry.file_name().into_string().unwrap(); + + if is_hidden(&file_name) && !hidden { + continue; + } + + let mode = meta_data.permissions().mode(); + let perms = unix_mode::to_string(mode); + + let modified = meta_data.modified().unwrap(); + let modified_datetime: DateTime = modified.into(); + + println!("{}\t1\t{}\t{}\t{}\t{}\t{}", + perms, + meta_data.st_uid(), + meta_data.st_gid(), + meta_data.len(), + modified_datetime.format("%b %e %k::%M"), + file_name, + ); + }, + Err(err) => { + eprintln!("ls: {}", err); + exit_code = 1; + }, + } + } + + exit_code +} + +/// Checks if a string looks like a hidden unix file +fn is_hidden(str: &str) -> bool { + str.starts_with('.') +} From a0b80b618424a1c1b028effa582fad7065574a27 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Mon, 12 Oct 2020 17:22:57 -0500 Subject: [PATCH 002/133] Ls: get a files group ownership --- ls/Cargo.toml | 1 + ls/src/main.rs | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/ls/Cargo.toml b/ls/Cargo.toml index ce01350b..56b9575d 100644 --- a/ls/Cargo.toml +++ b/ls/Cargo.toml @@ -11,6 +11,7 @@ description = "List information about the FILEs (the current directory by defaul [dependencies] clap = { version = "^2.33.0", features = ["wrap_help"] } +coreutils_core = { path = "../coreutils_core" } unix_mode = "0.1.1" chrono = "0.4" diff --git a/ls/src/main.rs b/ls/src/main.rs index 57c644ce..cc3191a2 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -2,6 +2,8 @@ use std::{fs, process}; use std::os::linux::fs::MetadataExt; use std::os::unix::fs::PermissionsExt; +use coreutils_core::os::group::Group; + extern crate chrono; use chrono::prelude::{DateTime, Utc}; @@ -77,10 +79,12 @@ fn print_list(dir: Vec, hidden: bool) -> i32 { let modified = meta_data.modified().unwrap(); let modified_datetime: DateTime = modified.into(); + let group = Group::from_gid(meta_data.st_gid()).unwrap(); + println!("{}\t1\t{}\t{}\t{}\t{}\t{}", perms, meta_data.st_uid(), - meta_data.st_gid(), + group.name(), meta_data.len(), modified_datetime.format("%b %e %k::%M"), file_name, From 60db2ca481e573474636dd32bbec4c77e598694e Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Mon, 12 Oct 2020 17:41:20 -0500 Subject: [PATCH 003/133] Ls: use consistent parameter name --- ls/src/main.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ls/src/main.rs b/ls/src/main.rs index cc3191a2..ef8c6384 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -44,13 +44,13 @@ fn main() { } } -fn print_default(dir: Vec, hidden: bool) -> i32 { +fn print_default(dir: Vec, all: bool) -> i32 { let exit_code = 1; for entry in dir { let file_name = entry.file_name().into_string().unwrap(); - if is_hidden(&file_name) && !hidden { + if is_hidden(&file_name) && !all { continue; } @@ -61,7 +61,7 @@ fn print_default(dir: Vec, hidden: bool) -> i32 { exit_code } -fn print_list(dir: Vec, hidden: bool) -> i32 { +fn print_list(dir: Vec, all: bool) -> i32 { let mut exit_code = 1; for entry in dir { @@ -69,7 +69,7 @@ fn print_list(dir: Vec, hidden: bool) -> i32 { Ok(meta_data) => { let file_name = entry.file_name().into_string().unwrap(); - if is_hidden(&file_name) && !hidden { + if is_hidden(&file_name) && !all { continue; } From b513919be393b0a63c427603db6337458d66f6c9 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Mon, 12 Oct 2020 17:46:24 -0500 Subject: [PATCH 004/133] Ls: get the file owner's name --- ls/src/main.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ls/src/main.rs b/ls/src/main.rs index ef8c6384..86c158d0 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -3,6 +3,7 @@ use std::os::linux::fs::MetadataExt; use std::os::unix::fs::PermissionsExt; use coreutils_core::os::group::Group; +use coreutils_core::os::passwd::Passwd; extern crate chrono; @@ -79,14 +80,16 @@ fn print_list(dir: Vec, all: bool) -> i32 { let modified = meta_data.modified().unwrap(); let modified_datetime: DateTime = modified.into(); + let user = Passwd::from_uid(meta_data.st_uid()).unwrap(); + let group = Group::from_gid(meta_data.st_gid()).unwrap(); println!("{}\t1\t{}\t{}\t{}\t{}\t{}", perms, - meta_data.st_uid(), + user.name(), group.name(), meta_data.len(), - modified_datetime.format("%b %e %k::%M"), + modified_datetime.format("%b %e %k:%M"), file_name, ); }, From f214d01ff85736f06a1acba7174f811ccf5cf068 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Mon, 12 Oct 2020 17:57:11 -0500 Subject: [PATCH 005/133] Ls: add comments --- ls/src/main.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ls/src/main.rs b/ls/src/main.rs index 86c158d0..494adf38 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -25,6 +25,7 @@ fn main() { Ok(dir) => { let mut dir: Vec<_> = dir.map(|r| r.unwrap()).collect(); + // Sort the directory entries by file name dir.sort_by_key(|dir| dir.path()); if list { @@ -45,6 +46,7 @@ fn main() { } } +/// Prints information about a file in the default format fn print_default(dir: Vec, all: bool) -> i32 { let exit_code = 1; @@ -62,6 +64,7 @@ fn print_default(dir: Vec, all: bool) -> i32 { exit_code } +/// Prints information about the provided file in a long format fn print_list(dir: Vec, all: bool) -> i32 { let mut exit_code = 1; From 93f318e0cc722c3526655a377df2ad100037a8e2 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Mon, 12 Oct 2020 18:24:34 -0500 Subject: [PATCH 006/133] Ls: attempt to count sub directories --- ls/Cargo.toml | 1 - ls/src/main.rs | 15 ++++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/ls/Cargo.toml b/ls/Cargo.toml index 56b9575d..958feb2c 100644 --- a/ls/Cargo.toml +++ b/ls/Cargo.toml @@ -15,6 +15,5 @@ coreutils_core = { path = "../coreutils_core" } unix_mode = "0.1.1" chrono = "0.4" - [build-dependencies] clap = "^2.33.0" diff --git a/ls/src/main.rs b/ls/src/main.rs index 494adf38..8cd1c98a 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -87,8 +87,21 @@ fn print_list(dir: Vec, all: bool) -> i32 { let group = Group::from_gid(meta_data.st_gid()).unwrap(); - println!("{}\t1\t{}\t{}\t{}\t{}\t{}", + let mut links = 1; + + if meta_data.is_dir() { + let subdir = fs::read_dir(entry.path()); + + if let Ok(subdir) = subdir { + let subdir_map = subdir.map(|r| r.unwrap()); + + links = 2 + subdir_map.filter(|r| fs::metadata(r.path()).unwrap().is_dir()).count(); + } + } + + println!("{}\t{}\t{}\t{}\t{}\t{}\t{}", perms, + links, user.name(), group.name(), meta_data.len(), From 3c55bcf5fe2fe8f88756d20ff9c0a4fbde8cc982 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Mon, 12 Oct 2020 18:27:12 -0500 Subject: [PATCH 007/133] Ls: run cargo fmt --- ls/build.rs | 2 +- ls/src/cli.rs | 6 +++--- ls/src/main.rs | 15 +++++++++------ 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/ls/build.rs b/ls/build.rs index 17f3863e..019855cd 100644 --- a/ls/build.rs +++ b/ls/build.rs @@ -13,7 +13,7 @@ fn main() { Err(err) => { eprintln!("No OUT_DIR: {}", err); return; - }, + } }; app.gen_completions("ls", Shell::Zsh, out_dir.clone()); diff --git a/ls/src/cli.rs b/ls/src/cli.rs index 402d5984..b44be774 100644 --- a/ls/src/cli.rs +++ b/ls/src/cli.rs @@ -16,17 +16,17 @@ pub(crate) fn create_app<'a, 'b>() -> App<'a, 'b> { .help("File(s) to list.") .required(true) .multiple(true) - .default_value(".") + .default_value("."), ) .arg( Arg::with_name("list") .help("Use a long listing format.") - .short("l") + .short("l"), ) .arg( Arg::with_name("all") .help("Do not ignore entries starting with .") .short("a") - .long("all") + .long("all"), ) } diff --git a/ls/src/main.rs b/ls/src/main.rs index 8cd1c98a..b29236b3 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -1,6 +1,6 @@ -use std::{fs, process}; use std::os::linux::fs::MetadataExt; use std::os::unix::fs::PermissionsExt; +use std::{fs, process}; use coreutils_core::os::group::Group; use coreutils_core::os::passwd::Passwd; @@ -37,7 +37,7 @@ fn main() { Err(err) => { eprintln!("ls: cannot access '{}': {}", file, err); exit_code = 1; - }, + } } } @@ -95,11 +95,14 @@ fn print_list(dir: Vec, all: bool) -> i32 { if let Ok(subdir) = subdir { let subdir_map = subdir.map(|r| r.unwrap()); - links = 2 + subdir_map.filter(|r| fs::metadata(r.path()).unwrap().is_dir()).count(); + links = 2 + subdir_map + .filter(|r| fs::metadata(r.path()).unwrap().is_dir()) + .count(); } } - println!("{}\t{}\t{}\t{}\t{}\t{}\t{}", + println!( + "{}\t{}\t{}\t{}\t{}\t{}\t{}", perms, links, user.name(), @@ -108,11 +111,11 @@ fn print_list(dir: Vec, all: bool) -> i32 { modified_datetime.format("%b %e %k:%M"), file_name, ); - }, + } Err(err) => { eprintln!("ls: {}", err); exit_code = 1; - }, + } } } From 56e0d01b181890af97c79115a80346dae3e6a669 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Tue, 13 Oct 2020 11:45:55 -0500 Subject: [PATCH 008/133] Ls: add time sort option --- ls/src/cli.rs | 14 ++++++++++---- ls/src/main.rs | 15 ++++++++++++--- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/ls/src/cli.rs b/ls/src/cli.rs index b44be774..85b5ecb0 100644 --- a/ls/src/cli.rs +++ b/ls/src/cli.rs @@ -18,15 +18,21 @@ pub(crate) fn create_app<'a, 'b>() -> App<'a, 'b> { .multiple(true) .default_value("."), ) + .arg( + Arg::with_name("all") + .help("Do not ignore entries starting with .") + .short("a") + .long("all"), + ) .arg( Arg::with_name("list") .help("Use a long listing format.") .short("l"), ) .arg( - Arg::with_name("all") - .help("Do not ignore entries starting with .") - .short("a") - .long("all"), + Arg::with_name("time") + .help("Sort by modification time, newest first.") + .short("t"), ) + } diff --git a/ls/src/main.rs b/ls/src/main.rs index b29236b3..693aff2d 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -15,8 +15,9 @@ fn main() { let matches = cli::create_app().get_matches(); let files = matches.values_of("FILE").unwrap(); - let list = matches.is_present("list"); let all = matches.is_present("all"); + let list = matches.is_present("list"); + let time = matches.is_present("time"); let mut exit_code = 0; @@ -25,8 +26,16 @@ fn main() { Ok(dir) => { let mut dir: Vec<_> = dir.map(|r| r.unwrap()).collect(); - // Sort the directory entries by file name - dir.sort_by_key(|dir| dir.path()); + if time { + dir.sort_by_key(|dir| { + let metadata = fs::metadata(dir.path()).expect("Failed to get metadata"); + + metadata.modified().expect("Failed to get file's modification time") + }); + } else { + // Sort the directory entries by file name by default + dir.sort_by_key(|dir| dir.path()); + } if list { exit_code = print_list(dir, all); From 5d9adc6ec6eec7d2c77cdc15f397abf17f24a4ae Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Tue, 13 Oct 2020 12:05:25 -0500 Subject: [PATCH 009/133] Ls: add option to reverse sort order --- ls/src/cli.rs | 6 ++++++ ls/src/main.rs | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/ls/src/cli.rs b/ls/src/cli.rs index 85b5ecb0..fb4a5cb5 100644 --- a/ls/src/cli.rs +++ b/ls/src/cli.rs @@ -29,6 +29,12 @@ pub(crate) fn create_app<'a, 'b>() -> App<'a, 'b> { .help("Use a long listing format.") .short("l"), ) + .arg( + Arg::with_name("reverse") + .help("Reverse order while sorting") + .short("r") + .long("reverse") + ) .arg( Arg::with_name("time") .help("Sort by modification time, newest first.") diff --git a/ls/src/main.rs b/ls/src/main.rs index 693aff2d..ab66b01d 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -17,6 +17,7 @@ fn main() { let files = matches.values_of("FILE").unwrap(); let all = matches.is_present("all"); let list = matches.is_present("list"); + let reverse = matches.is_present("reverse"); let time = matches.is_present("time"); let mut exit_code = 0; @@ -37,6 +38,10 @@ fn main() { dir.sort_by_key(|dir| dir.path()); } + if reverse { + dir.reverse(); + } + if list { exit_code = print_list(dir, all); } else { From a0499b110f86816074fdaed71c660fad277e3166 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Tue, 13 Oct 2020 13:37:41 -0500 Subject: [PATCH 010/133] Ls: create flags struct --- ls/src/main.rs | 55 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 39 insertions(+), 16 deletions(-) diff --git a/ls/src/main.rs b/ls/src/main.rs index ab66b01d..ba9d5957 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -1,10 +1,12 @@ -use std::os::linux::fs::MetadataExt; -use std::os::unix::fs::PermissionsExt; -use std::{fs, process}; +use clap::ArgMatches; use coreutils_core::os::group::Group; use coreutils_core::os::passwd::Passwd; +use std::os::linux::fs::MetadataExt; +use std::os::unix::fs::PermissionsExt; +use std::{fs, process}; + extern crate chrono; use chrono::prelude::{DateTime, Utc}; @@ -15,10 +17,7 @@ fn main() { let matches = cli::create_app().get_matches(); let files = matches.values_of("FILE").unwrap(); - let all = matches.is_present("all"); - let list = matches.is_present("list"); - let reverse = matches.is_present("reverse"); - let time = matches.is_present("time"); + let flags = LsFlags::from_matches(&matches); let mut exit_code = 0; @@ -27,7 +26,7 @@ fn main() { Ok(dir) => { let mut dir: Vec<_> = dir.map(|r| r.unwrap()).collect(); - if time { + if flags.time { dir.sort_by_key(|dir| { let metadata = fs::metadata(dir.path()).expect("Failed to get metadata"); @@ -38,14 +37,14 @@ fn main() { dir.sort_by_key(|dir| dir.path()); } - if reverse { + if flags.reverse { dir.reverse(); } - if list { - exit_code = print_list(dir, all); + if flags.list { + exit_code = print_list(dir, flags); } else { - exit_code = print_default(dir, all); + exit_code = print_default(dir, flags); } } Err(err) => { @@ -61,13 +60,13 @@ fn main() { } /// Prints information about a file in the default format -fn print_default(dir: Vec, all: bool) -> i32 { +fn print_default(dir: Vec, flags: LsFlags) -> i32 { let exit_code = 1; for entry in dir { let file_name = entry.file_name().into_string().unwrap(); - if is_hidden(&file_name) && !all { + if is_hidden(&file_name) && !flags.all { continue; } @@ -79,7 +78,7 @@ fn print_default(dir: Vec, all: bool) -> i32 { } /// Prints information about the provided file in a long format -fn print_list(dir: Vec, all: bool) -> i32 { +fn print_list(dir: Vec, flags: LsFlags) -> i32 { let mut exit_code = 1; for entry in dir { @@ -87,7 +86,7 @@ fn print_list(dir: Vec, all: bool) -> i32 { Ok(meta_data) => { let file_name = entry.file_name().into_string().unwrap(); - if is_hidden(&file_name) && !all { + if is_hidden(&file_name) && !flags.all { continue; } @@ -140,3 +139,27 @@ fn print_list(dir: Vec, all: bool) -> i32 { fn is_hidden(str: &str) -> bool { str.starts_with('.') } + +#[derive(Default, Copy, Clone)] +struct LsFlags { + all: bool, + list: bool, + reverse: bool, + time: bool, +} + +impl LsFlags { + fn from_matches(matches: &ArgMatches<'_>) -> Self { + let all = matches.is_present("all"); + let list = matches.is_present("list"); + let reverse = matches.is_present("reverse"); + let time = matches.is_present("time"); + + LsFlags { + all, + list, + reverse, + time, + } + } +} From dd5eff9ac9f1811c6d3af0fc5d8d1c9059111f4e Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Tue, 13 Oct 2020 18:07:53 -0500 Subject: [PATCH 011/133] Ls: color file names --- ls/Cargo.toml | 1 + ls/src/main.rs | 14 +++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/ls/Cargo.toml b/ls/Cargo.toml index 958feb2c..382d6cb7 100644 --- a/ls/Cargo.toml +++ b/ls/Cargo.toml @@ -14,6 +14,7 @@ clap = { version = "^2.33.0", features = ["wrap_help"] } coreutils_core = { path = "../coreutils_core" } unix_mode = "0.1.1" chrono = "0.4" +ansi_term = "0.12.1" [build-dependencies] clap = "^2.33.0" diff --git a/ls/src/main.rs b/ls/src/main.rs index ba9d5957..070e8288 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -7,6 +7,8 @@ use std::os::linux::fs::MetadataExt; use std::os::unix::fs::PermissionsExt; use std::{fs, process}; +use ansi_term::Color; + extern crate chrono; use chrono::prelude::{DateTime, Utc}; @@ -84,7 +86,7 @@ fn print_list(dir: Vec, flags: LsFlags) -> i32 { for entry in dir { match fs::metadata(entry.path()) { Ok(meta_data) => { - let file_name = entry.file_name().into_string().unwrap(); + let mut file_name = entry.file_name().into_string().unwrap(); if is_hidden(&file_name) && !flags.all { continue; @@ -102,7 +104,13 @@ fn print_list(dir: Vec, flags: LsFlags) -> i32 { let mut links = 1; + if is_executable(meta_data.permissions()) { + file_name = Color::Green.bold().paint(file_name).to_string(); + } + if meta_data.is_dir() { + file_name = Color::Blue.bold().paint(file_name).to_string(); + let subdir = fs::read_dir(entry.path()); if let Ok(subdir) = subdir { @@ -135,6 +143,10 @@ fn print_list(dir: Vec, flags: LsFlags) -> i32 { exit_code } +fn is_executable(permissions: fs::Permissions) -> bool { + permissions.mode() & 0o111 != 0 +} + /// Checks if a string looks like a hidden unix file fn is_hidden(str: &str) -> bool { str.starts_with('.') From 458ff532aa00e71ee98cd49a4b6dd7bad914bcaf Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Tue, 13 Oct 2020 18:08:39 -0500 Subject: [PATCH 012/133] Ls: convert all names to lowercase before sorting --- ls/src/main.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ls/src/main.rs b/ls/src/main.rs index 070e8288..d9d2be4c 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -36,7 +36,11 @@ fn main() { }); } else { // Sort the directory entries by file name by default - dir.sort_by_key(|dir| dir.path()); + dir.sort_by_key(|dir| { + let file_name = dir.file_name().into_string().unwrap(); + + file_name.to_lowercase() + }); } if flags.reverse { From 25a19e0bc62771c177793ea7897bb740aafc33b0 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Tue, 13 Oct 2020 18:24:57 -0500 Subject: [PATCH 013/133] Ls: ensure an item is a file --- ls/src/main.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/ls/src/main.rs b/ls/src/main.rs index d9d2be4c..693fdcab 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -89,30 +89,30 @@ fn print_list(dir: Vec, flags: LsFlags) -> i32 { for entry in dir { match fs::metadata(entry.path()) { - Ok(meta_data) => { + Ok(metadata) => { let mut file_name = entry.file_name().into_string().unwrap(); if is_hidden(&file_name) && !flags.all { continue; } - let mode = meta_data.permissions().mode(); + let mode = metadata.permissions().mode(); let perms = unix_mode::to_string(mode); - let modified = meta_data.modified().unwrap(); + let modified = metadata.modified().unwrap(); let modified_datetime: DateTime = modified.into(); - let user = Passwd::from_uid(meta_data.st_uid()).unwrap(); + let user = Passwd::from_uid(metadata.st_uid()).unwrap(); - let group = Group::from_gid(meta_data.st_gid()).unwrap(); + let group = Group::from_gid(metadata.st_gid()).unwrap(); let mut links = 1; - if is_executable(meta_data.permissions()) { + if is_executable(&metadata) { file_name = Color::Green.bold().paint(file_name).to_string(); } - if meta_data.is_dir() { + if metadata.is_dir() { file_name = Color::Blue.bold().paint(file_name).to_string(); let subdir = fs::read_dir(entry.path()); @@ -132,7 +132,7 @@ fn print_list(dir: Vec, flags: LsFlags) -> i32 { links, user.name(), group.name(), - meta_data.len(), + metadata.len(), modified_datetime.format("%b %e %k:%M"), file_name, ); @@ -147,8 +147,8 @@ fn print_list(dir: Vec, flags: LsFlags) -> i32 { exit_code } -fn is_executable(permissions: fs::Permissions) -> bool { - permissions.mode() & 0o111 != 0 +fn is_executable(metadata: &fs::Metadata) -> bool { + metadata.is_file() && metadata.permissions().mode() & 0o111 != 0 } /// Checks if a string looks like a hidden unix file From 9c2ff1df06d40ea7ee84e4436fe30b17ca0fe1f7 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Wed, 14 Oct 2020 07:53:10 -0500 Subject: [PATCH 014/133] Ls: color default listing --- ls/src/main.rs | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/ls/src/main.rs b/ls/src/main.rs index 693fdcab..cb8ee914 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -70,12 +70,24 @@ fn print_default(dir: Vec, flags: LsFlags) -> i32 { let exit_code = 1; for entry in dir { - let file_name = entry.file_name().into_string().unwrap(); + let mut file_name = entry.file_name().into_string().unwrap(); if is_hidden(&file_name) && !flags.all { continue; } + let metadata = fs::metadata(entry.path()); + + if let Ok(metadata) = metadata { + if is_executable(&metadata) { + file_name = add_executable_color(file_name); + } + + if metadata.is_dir() { + file_name = add_directory_color(file_name); + } + } + print!("{} ", file_name); } println!(); @@ -109,11 +121,11 @@ fn print_list(dir: Vec, flags: LsFlags) -> i32 { let mut links = 1; if is_executable(&metadata) { - file_name = Color::Green.bold().paint(file_name).to_string(); + file_name = add_executable_color(file_name); } if metadata.is_dir() { - file_name = Color::Blue.bold().paint(file_name).to_string(); + file_name = add_directory_color(file_name); let subdir = fs::read_dir(entry.path()); @@ -147,6 +159,14 @@ fn print_list(dir: Vec, flags: LsFlags) -> i32 { exit_code } +fn add_executable_color(file_name: String) -> String { + Color::Green.bold().paint(file_name).to_string() +} + +fn add_directory_color(directory_name: String) -> String { + Color::Blue.bold().paint(directory_name).to_string() +} + fn is_executable(metadata: &fs::Metadata) -> bool { metadata.is_file() && metadata.permissions().mode() & 0o111 != 0 } From 3cca096780848c9c11468eaff027dffdd28a8e01 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Wed, 14 Oct 2020 07:54:49 -0500 Subject: [PATCH 015/133] Ls: format code --- ls/src/cli.rs | 3 +-- ls/src/main.rs | 7 +------ 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/ls/src/cli.rs b/ls/src/cli.rs index fb4a5cb5..a764c72e 100644 --- a/ls/src/cli.rs +++ b/ls/src/cli.rs @@ -33,12 +33,11 @@ pub(crate) fn create_app<'a, 'b>() -> App<'a, 'b> { Arg::with_name("reverse") .help("Reverse order while sorting") .short("r") - .long("reverse") + .long("reverse"), ) .arg( Arg::with_name("time") .help("Sort by modification time, newest first.") .short("t"), ) - } diff --git a/ls/src/main.rs b/ls/src/main.rs index cb8ee914..220b33d2 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -191,11 +191,6 @@ impl LsFlags { let reverse = matches.is_present("reverse"); let time = matches.is_present("time"); - LsFlags { - all, - list, - reverse, - time, - } + LsFlags { all, list, reverse, time } } } From 97bb47e0811a0b83efb4cc5400b2575fe3a20625 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Wed, 14 Oct 2020 13:05:05 -0500 Subject: [PATCH 016/133] Ls: format symlinks --- ls/src/main.rs | 81 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 57 insertions(+), 24 deletions(-) diff --git a/ls/src/main.rs b/ls/src/main.rs index 220b33d2..3cf72521 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -3,9 +3,10 @@ use clap::ArgMatches; use coreutils_core::os::group::Group; use coreutils_core::os::passwd::Passwd; +use std::string::String; use std::os::linux::fs::MetadataExt; use std::os::unix::fs::PermissionsExt; -use std::{fs, process}; +use std::{fs, path, process}; use ansi_term::Color; @@ -70,24 +71,12 @@ fn print_default(dir: Vec, flags: LsFlags) -> i32 { let exit_code = 1; for entry in dir { - let mut file_name = entry.file_name().into_string().unwrap(); + let file_name = get_file_name(&entry); if is_hidden(&file_name) && !flags.all { continue; } - let metadata = fs::metadata(entry.path()); - - if let Ok(metadata) = metadata { - if is_executable(&metadata) { - file_name = add_executable_color(file_name); - } - - if metadata.is_dir() { - file_name = add_directory_color(file_name); - } - } - print!("{} ", file_name); } println!(); @@ -100,9 +89,9 @@ fn print_list(dir: Vec, flags: LsFlags) -> i32 { let mut exit_code = 1; for entry in dir { - match fs::metadata(entry.path()) { + match fs::symlink_metadata(entry.path()) { Ok(metadata) => { - let mut file_name = entry.file_name().into_string().unwrap(); + let file_name = get_file_name(&entry); if is_hidden(&file_name) && !flags.all { continue; @@ -120,13 +109,7 @@ fn print_list(dir: Vec, flags: LsFlags) -> i32 { let mut links = 1; - if is_executable(&metadata) { - file_name = add_executable_color(file_name); - } - if metadata.is_dir() { - file_name = add_directory_color(file_name); - let subdir = fs::read_dir(entry.path()); if let Ok(subdir) = subdir { @@ -159,16 +142,66 @@ fn print_list(dir: Vec, flags: LsFlags) -> i32 { exit_code } +/// Gets a file name from a directory entry and adds appropriate formatting +fn get_file_name(file: &fs::DirEntry) -> String { + let mut file_name = file.file_name().into_string().unwrap(); + + let metadata = fs::symlink_metadata(file.path()); + + if let Ok(metadata) = metadata { + if is_executable(&file.path()) { + file_name = add_executable_color(file_name); + } + + if metadata.file_type().is_symlink() { + file_name = add_symlink_color(file_name); + let symlink = fs::read_link(file.path()); + + if let Ok(symlink) = symlink { + let mut symlink_name = String::from(symlink.to_str().unwrap()); + + if is_executable(&symlink) { + symlink_name = add_executable_color(symlink_name); + } + + file_name = format!("{} -> {}", file_name, symlink_name); + } + } + + if metadata.is_dir() { + file_name = add_directory_color(file_name); + } + } + + file_name +} + +/// Adds a bold green color to a file name to represent an executable fn add_executable_color(file_name: String) -> String { Color::Green.bold().paint(file_name).to_string() } +/// Adds a bold blue color to a directory name fn add_directory_color(directory_name: String) -> String { Color::Blue.bold().paint(directory_name).to_string() } -fn is_executable(metadata: &fs::Metadata) -> bool { - metadata.is_file() && metadata.permissions().mode() & 0o111 != 0 +/// Adds a bold cyan color to a file name to represent a symlink +fn add_symlink_color(symlink_name: String) -> String { + Color::Cyan.bold().paint(symlink_name).to_string() +} + +/// Check if a path is an executable file +fn is_executable(path: &path::PathBuf) -> bool { + let mut result = false; + + let metadata = fs::symlink_metadata(path); + + if let Ok(metadata) = metadata { + result = metadata.is_file() && metadata.permissions().mode() & 0o111 != 0; + } + + result } /// Checks if a string looks like a hidden unix file From f5744893e42bcc63797af5a7f19db19a915b265e Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Wed, 14 Oct 2020 13:10:41 -0500 Subject: [PATCH 017/133] Ls: move sorting to functions --- ls/src/main.rs | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/ls/src/main.rs b/ls/src/main.rs index 3cf72521..2681ff5e 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -7,6 +7,7 @@ use std::string::String; use std::os::linux::fs::MetadataExt; use std::os::unix::fs::PermissionsExt; use std::{fs, path, process}; +use std::time::SystemTime; use ansi_term::Color; @@ -30,18 +31,10 @@ fn main() { let mut dir: Vec<_> = dir.map(|r| r.unwrap()).collect(); if flags.time { - dir.sort_by_key(|dir| { - let metadata = fs::metadata(dir.path()).expect("Failed to get metadata"); - - metadata.modified().expect("Failed to get file's modification time") - }); + dir.sort_by_key(sort_by_time); } else { // Sort the directory entries by file name by default - dir.sort_by_key(|dir| { - let file_name = dir.file_name().into_string().unwrap(); - - file_name.to_lowercase() - }); + dir.sort_by_key(sort_by_name); } if flags.reverse { @@ -209,6 +202,20 @@ fn is_hidden(str: &str) -> bool { str.starts_with('.') } +/// Sort a list of directories by file name alphabetically +fn sort_by_name(dir: &fs::DirEntry) -> String { + let file_name = dir.file_name().into_string().unwrap(); + + file_name.to_lowercase() +} + +/// Sort a list of directories by modification time +fn sort_by_time(dir: &fs::DirEntry) -> SystemTime { + let metadata = fs::metadata(dir.path()).expect("Failed to get metadata"); + + metadata.modified().expect("Failed to get file's modification time") +} + #[derive(Default, Copy, Clone)] struct LsFlags { all: bool, From ee46d4ceac151814d5f042548a0a843d24b2c719 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Wed, 14 Oct 2020 16:11:40 -0500 Subject: [PATCH 018/133] Ls: retrieve hard link count with metadata method --- ls/src/main.rs | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/ls/src/main.rs b/ls/src/main.rs index 2681ff5e..cfbe6c46 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -5,7 +5,7 @@ use coreutils_core::os::passwd::Passwd; use std::string::String; use std::os::linux::fs::MetadataExt; -use std::os::unix::fs::PermissionsExt; +use std::os::unix::fs::{MetadataExt as UnixMetadata, PermissionsExt}; use std::{fs, path, process}; use std::time::SystemTime; @@ -100,24 +100,10 @@ fn print_list(dir: Vec, flags: LsFlags) -> i32 { let group = Group::from_gid(metadata.st_gid()).unwrap(); - let mut links = 1; - - if metadata.is_dir() { - let subdir = fs::read_dir(entry.path()); - - if let Ok(subdir) = subdir { - let subdir_map = subdir.map(|r| r.unwrap()); - - links = 2 + subdir_map - .filter(|r| fs::metadata(r.path()).unwrap().is_dir()) - .count(); - } - } - println!( "{}\t{}\t{}\t{}\t{}\t{}\t{}", perms, - links, + metadata.nlink(), user.name(), group.name(), metadata.len(), From c9c04266850bef35f32a3ae0ef6bcc0c30d8a074 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Wed, 14 Oct 2020 16:59:08 -0500 Subject: [PATCH 019/133] Ls: add option to print blocks --- ls/src/cli.rs | 12 +++++++++--- ls/src/main.rs | 47 +++++++++++++++++++++++++++++++---------------- 2 files changed, 40 insertions(+), 19 deletions(-) diff --git a/ls/src/cli.rs b/ls/src/cli.rs index a764c72e..e98780f8 100644 --- a/ls/src/cli.rs +++ b/ls/src/cli.rs @@ -13,7 +13,7 @@ pub(crate) fn create_app<'a, 'b>() -> App<'a, 'b> { .settings(&[ColoredHelp]) .arg( Arg::with_name("FILE") - .help("File(s) to list.") + .help("File(s) to list") .required(true) .multiple(true) .default_value("."), @@ -24,9 +24,15 @@ pub(crate) fn create_app<'a, 'b>() -> App<'a, 'b> { .short("a") .long("all"), ) + .arg( + Arg::with_name("size") + .help("Print the allocated size of each file, in blocks") + .short("s") + .long("size") + ) .arg( Arg::with_name("list") - .help("Use a long listing format.") + .help("Use a long listing format") .short("l"), ) .arg( @@ -37,7 +43,7 @@ pub(crate) fn create_app<'a, 'b>() -> App<'a, 'b> { ) .arg( Arg::with_name("time") - .help("Sort by modification time, newest first.") + .help("Sort by modification time, newest first") .short("t"), ) } diff --git a/ls/src/main.rs b/ls/src/main.rs index cfbe6c46..9b89f653 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -4,7 +4,6 @@ use coreutils_core::os::group::Group; use coreutils_core::os::passwd::Passwd; use std::string::String; -use std::os::linux::fs::MetadataExt; use std::os::unix::fs::{MetadataExt as UnixMetadata, PermissionsExt}; use std::{fs, path, process}; use std::time::SystemTime; @@ -96,20 +95,34 @@ fn print_list(dir: Vec, flags: LsFlags) -> i32 { let modified = metadata.modified().unwrap(); let modified_datetime: DateTime = modified.into(); - let user = Passwd::from_uid(metadata.st_uid()).unwrap(); - - let group = Group::from_gid(metadata.st_gid()).unwrap(); - - println!( - "{}\t{}\t{}\t{}\t{}\t{}\t{}", - perms, - metadata.nlink(), - user.name(), - group.name(), - metadata.len(), - modified_datetime.format("%b %e %k:%M"), - file_name, - ); + let user = Passwd::from_uid(metadata.uid()).unwrap(); + + let group = Group::from_gid(metadata.gid()).unwrap(); + + if flags.size { + println!( + "{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}", + metadata.blocks(), + perms, + metadata.nlink(), + user.name(), + group.name(), + metadata.len(), + modified_datetime.format("%b %e %k:%M"), + file_name, + ); + } else { + println!( + "{}\t{}\t{}\t{}\t{}\t{}\t{}", + perms, + metadata.nlink(), + user.name(), + group.name(), + metadata.len(), + modified_datetime.format("%b %e %k:%M"), + file_name, + ); + } } Err(err) => { eprintln!("ls: {}", err); @@ -207,6 +220,7 @@ struct LsFlags { all: bool, list: bool, reverse: bool, + size: bool, time: bool, } @@ -215,8 +229,9 @@ impl LsFlags { let all = matches.is_present("all"); let list = matches.is_present("list"); let reverse = matches.is_present("reverse"); + let size = matches.is_present("size"); let time = matches.is_present("time"); - LsFlags { all, list, reverse, time } + LsFlags { all, list, reverse, size, time } } } From c1f94cd004e9598cde9154688a04498c6f92ed03 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Wed, 14 Oct 2020 17:02:51 -0500 Subject: [PATCH 020/133] Ls: check filename without styles applied --- ls/src/main.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/ls/src/main.rs b/ls/src/main.rs index 9b89f653..e3fa759e 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -65,7 +65,7 @@ fn print_default(dir: Vec, flags: LsFlags) -> i32 { for entry in dir { let file_name = get_file_name(&entry); - if is_hidden(&file_name) && !flags.all { + if is_hidden(&entry) && !flags.all { continue; } @@ -85,7 +85,7 @@ fn print_list(dir: Vec, flags: LsFlags) -> i32 { Ok(metadata) => { let file_name = get_file_name(&entry); - if is_hidden(&file_name) && !flags.all { + if is_hidden(&entry) && !flags.all { continue; } @@ -197,8 +197,16 @@ fn is_executable(path: &path::PathBuf) -> bool { } /// Checks if a string looks like a hidden unix file -fn is_hidden(str: &str) -> bool { - str.starts_with('.') +fn is_hidden(entry: &fs::DirEntry) -> bool { + let mut result = false; + + let file_name = entry.file_name().into_string(); + + if let Ok(file_name) = file_name { + result = file_name.starts_with('.') + } + + result } /// Sort a list of directories by file name alphabetically From ed240f1103415d4fc1a02c4dac26889f134f153c Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Wed, 14 Oct 2020 18:54:23 -0500 Subject: [PATCH 021/133] Ls: add fallback time --- ls/src/main.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ls/src/main.rs b/ls/src/main.rs index e3fa759e..2c047949 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -218,9 +218,13 @@ fn sort_by_name(dir: &fs::DirEntry) -> String { /// Sort a list of directories by modification time fn sort_by_time(dir: &fs::DirEntry) -> SystemTime { - let metadata = fs::metadata(dir.path()).expect("Failed to get metadata"); + let metadata = fs::metadata(dir.path()); - metadata.modified().expect("Failed to get file's modification time") + if let Ok(metadata) = metadata { + metadata.modified().unwrap() + } else { + SystemTime::now() + } } #[derive(Default, Copy, Clone)] From 5546fbc59e11756426c2b4d413517cb8ecac8899 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Wed, 14 Oct 2020 18:54:32 -0500 Subject: [PATCH 022/133] Ls: reverse date sort by default --- ls/src/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/ls/src/main.rs b/ls/src/main.rs index 2c047949..86b76a69 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -31,6 +31,7 @@ fn main() { if flags.time { dir.sort_by_key(sort_by_time); + dir.reverse(); } else { // Sort the directory entries by file name by default dir.sort_by_key(sort_by_name); From 5e448d32deec0aa37b0119c74ed3dcaa64abfc2e Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Thu, 15 Oct 2020 10:13:13 -0500 Subject: [PATCH 023/133] Ls: reduce duplicated code --- ls/src/main.rs | 47 +++++++++++++++++++---------------------------- 1 file changed, 19 insertions(+), 28 deletions(-) diff --git a/ls/src/main.rs b/ls/src/main.rs index 86b76a69..2b7d65a2 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -3,9 +3,9 @@ use clap::ArgMatches; use coreutils_core::os::group::Group; use coreutils_core::os::passwd::Passwd; -use std::string::String; -use std::os::unix::fs::{MetadataExt as UnixMetadata, PermissionsExt}; use std::{fs, path, process}; +use std::os::unix::fs::{MetadataExt as UnixMetadata, PermissionsExt}; +use std::string::String; use std::time::SystemTime; use ansi_term::Color; @@ -90,40 +90,31 @@ fn print_list(dir: Vec, flags: LsFlags) -> i32 { continue; } + if flags.size { + print!("{:>3} ", metadata.blocks()); + } + let mode = metadata.permissions().mode(); let perms = unix_mode::to_string(mode); + print!("{}\t", perms); - let modified = metadata.modified().unwrap(); - let modified_datetime: DateTime = modified.into(); + print!("{:>3} ", metadata.nlink()); let user = Passwd::from_uid(metadata.uid()).unwrap(); + print!("{}\t", user.name()); let group = Group::from_gid(metadata.gid()).unwrap(); + print!("{}\t", group.name()); - if flags.size { - println!( - "{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}", - metadata.blocks(), - perms, - metadata.nlink(), - user.name(), - group.name(), - metadata.len(), - modified_datetime.format("%b %e %k:%M"), - file_name, - ); - } else { - println!( - "{}\t{}\t{}\t{}\t{}\t{}\t{}", - perms, - metadata.nlink(), - user.name(), - group.name(), - metadata.len(), - modified_datetime.format("%b %e %k:%M"), - file_name, - ); - } + print!("{:>5} ", metadata.len()); + + let modified = metadata.modified().unwrap(); + let modified_datetime: DateTime = modified.into(); + print!("{} ", modified_datetime.format("%b %e %k:%M")); + + print!("{}", file_name); + + println!(); } Err(err) => { eprintln!("ls: {}", err); From e860ec62e919d26b175bd866463b9e8adcf00ec3 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Thu, 15 Oct 2020 14:31:38 -0500 Subject: [PATCH 024/133] Ls: add -g flag --- ls/src/cli.rs | 17 +++++++++++------ ls/src/main.rs | 14 +++++++++----- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/ls/src/cli.rs b/ls/src/cli.rs index e98780f8..6ab126c3 100644 --- a/ls/src/cli.rs +++ b/ls/src/cli.rs @@ -24,23 +24,28 @@ pub(crate) fn create_app<'a, 'b>() -> App<'a, 'b> { .short("a") .long("all"), ) - .arg( - Arg::with_name("size") - .help("Print the allocated size of each file, in blocks") - .short("s") - .long("size") - ) .arg( Arg::with_name("list") .help("Use a long listing format") .short("l"), ) + .arg( + Arg::with_name("no_owner") + .help("like -l, but do not list owner") + .short("g") + ) .arg( Arg::with_name("reverse") .help("Reverse order while sorting") .short("r") .long("reverse"), ) + .arg( + Arg::with_name("size") + .help("Print the allocated size of each file, in blocks") + .short("s") + .long("size") + ) .arg( Arg::with_name("time") .help("Sort by modification time, newest first") diff --git a/ls/src/main.rs b/ls/src/main.rs index 2b7d65a2..b41bc37a 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -41,7 +41,7 @@ fn main() { dir.reverse(); } - if flags.list { + if flags.list || flags.no_owner { exit_code = print_list(dir, flags); } else { exit_code = print_default(dir, flags); @@ -96,15 +96,17 @@ fn print_list(dir: Vec, flags: LsFlags) -> i32 { let mode = metadata.permissions().mode(); let perms = unix_mode::to_string(mode); - print!("{}\t", perms); + print!("{} ", perms); print!("{:>3} ", metadata.nlink()); let user = Passwd::from_uid(metadata.uid()).unwrap(); print!("{}\t", user.name()); - let group = Group::from_gid(metadata.gid()).unwrap(); - print!("{}\t", group.name()); + if !flags.no_owner { + let group = Group::from_gid(metadata.gid()).unwrap(); + print!("{}\t", group.name()); + } print!("{:>5} ", metadata.len()); @@ -223,6 +225,7 @@ fn sort_by_time(dir: &fs::DirEntry) -> SystemTime { struct LsFlags { all: bool, list: bool, + no_owner: bool, reverse: bool, size: bool, time: bool, @@ -232,10 +235,11 @@ impl LsFlags { fn from_matches(matches: &ArgMatches<'_>) -> Self { let all = matches.is_present("all"); let list = matches.is_present("list"); + let no_owner = matches.is_present("no_owner"); let reverse = matches.is_present("reverse"); let size = matches.is_present("size"); let time = matches.is_present("time"); - LsFlags { all, list, reverse, size, time } + LsFlags { all, list, no_owner, reverse, size, time } } } From eae76a745ca6f9dab83fcabb01204f699d5cd27a Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Thu, 15 Oct 2020 14:36:44 -0500 Subject: [PATCH 025/133] Ls: add -m option --- ls/src/cli.rs | 5 +++++ ls/src/main.rs | 12 +++++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/ls/src/cli.rs b/ls/src/cli.rs index 6ab126c3..8a2b12a5 100644 --- a/ls/src/cli.rs +++ b/ls/src/cli.rs @@ -24,6 +24,11 @@ pub(crate) fn create_app<'a, 'b>() -> App<'a, 'b> { .short("a") .long("all"), ) + .arg( + Arg::with_name("comma_separate") + .help("fill width with a comma separated list of entries") + .short("m") + ) .arg( Arg::with_name("list") .help("Use a long listing format") diff --git a/ls/src/main.rs b/ls/src/main.rs index b41bc37a..e6390cfd 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -41,7 +41,7 @@ fn main() { dir.reverse(); } - if flags.list || flags.no_owner { + if !flags.comma_separate && flags.list || flags.no_owner { exit_code = print_list(dir, flags); } else { exit_code = print_default(dir, flags); @@ -70,7 +70,11 @@ fn print_default(dir: Vec, flags: LsFlags) -> i32 { continue; } - print!("{} ", file_name); + if flags.comma_separate { + print!("{}, ", file_name); + } else { + print!("{} ", file_name); + } } println!(); @@ -224,6 +228,7 @@ fn sort_by_time(dir: &fs::DirEntry) -> SystemTime { #[derive(Default, Copy, Clone)] struct LsFlags { all: bool, + comma_separate: bool, list: bool, no_owner: bool, reverse: bool, @@ -234,12 +239,13 @@ struct LsFlags { impl LsFlags { fn from_matches(matches: &ArgMatches<'_>) -> Self { let all = matches.is_present("all"); + let comma_separate = matches.is_present("comma_separate"); let list = matches.is_present("list"); let no_owner = matches.is_present("no_owner"); let reverse = matches.is_present("reverse"); let size = matches.is_present("size"); let time = matches.is_present("time"); - LsFlags { all, list, no_owner, reverse, size, time } + LsFlags { all, comma_separate, list, no_owner, reverse, size, time } } } From 55a3d46984b81b82f4008dd230b5af2d2e805c13 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Thu, 15 Oct 2020 14:57:13 -0500 Subject: [PATCH 026/133] Ls: don't show symlink source name with -m flag --- ls/src/main.rs | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/ls/src/main.rs b/ls/src/main.rs index e6390cfd..4ab4a534 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -64,7 +64,7 @@ fn print_default(dir: Vec, flags: LsFlags) -> i32 { let exit_code = 1; for entry in dir { - let file_name = get_file_name(&entry); + let file_name = get_file_name(&entry, flags); if is_hidden(&entry) && !flags.all { continue; @@ -88,7 +88,7 @@ fn print_list(dir: Vec, flags: LsFlags) -> i32 { for entry in dir { match fs::symlink_metadata(entry.path()) { Ok(metadata) => { - let file_name = get_file_name(&entry); + let file_name = get_file_name(&entry, flags); if is_hidden(&entry) && !flags.all { continue; @@ -133,7 +133,7 @@ fn print_list(dir: Vec, flags: LsFlags) -> i32 { } /// Gets a file name from a directory entry and adds appropriate formatting -fn get_file_name(file: &fs::DirEntry) -> String { +fn get_file_name(file: &fs::DirEntry, flags: LsFlags) -> String { let mut file_name = file.file_name().into_string().unwrap(); let metadata = fs::symlink_metadata(file.path()); @@ -145,16 +145,19 @@ fn get_file_name(file: &fs::DirEntry) -> String { if metadata.file_type().is_symlink() { file_name = add_symlink_color(file_name); - let symlink = fs::read_link(file.path()); - if let Ok(symlink) = symlink { - let mut symlink_name = String::from(symlink.to_str().unwrap()); + if flags.list || flags.no_owner { + let symlink = fs::read_link(file.path()); - if is_executable(&symlink) { - symlink_name = add_executable_color(symlink_name); - } + if let Ok(symlink) = symlink { + let mut symlink_name = String::from(symlink.to_str().unwrap()); + + if is_executable(&symlink) { + symlink_name = add_executable_color(symlink_name); + } - file_name = format!("{} -> {}", file_name, symlink_name); + file_name = format!("{} -> {}", file_name, symlink_name); + } } } From 22ceb15c9f1ea9c73f765c21a61f4b76d6560fdf Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Fri, 16 Oct 2020 10:26:26 -0500 Subject: [PATCH 027/133] Ls: add numeric-uid-gid flag --- ls/src/cli.rs | 6 ++++++ ls/src/main.rs | 27 +++++++++++++++++++++------ 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/ls/src/cli.rs b/ls/src/cli.rs index 8a2b12a5..5eac2cac 100644 --- a/ls/src/cli.rs +++ b/ls/src/cli.rs @@ -39,6 +39,12 @@ pub(crate) fn create_app<'a, 'b>() -> App<'a, 'b> { .help("like -l, but do not list owner") .short("g") ) + .arg( + Arg::with_name("numeric_uid_gid") + .help("like -l, but list numeric user and group IDs") + .short("n") + .long("numeric-uid-gid") + ) .arg( Arg::with_name("reverse") .help("Reverse order while sorting") diff --git a/ls/src/main.rs b/ls/src/main.rs index 4ab4a534..38d1d91c 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -41,7 +41,7 @@ fn main() { dir.reverse(); } - if !flags.comma_separate && flags.list || flags.no_owner { + if !flags.comma_separate && flags.show_list() { exit_code = print_list(dir, flags); } else { exit_code = print_default(dir, flags); @@ -104,12 +104,20 @@ fn print_list(dir: Vec, flags: LsFlags) -> i32 { print!("{:>3} ", metadata.nlink()); - let user = Passwd::from_uid(metadata.uid()).unwrap(); - print!("{}\t", user.name()); + if flags.numeric_uid_gid { + print!("{}\t", metadata.uid()); + } else { + let user = Passwd::from_uid(metadata.uid()).unwrap(); + print!("{}\t", user.name()); + } if !flags.no_owner { - let group = Group::from_gid(metadata.gid()).unwrap(); - print!("{}\t", group.name()); + if flags.numeric_uid_gid { + print!("{}\t", metadata.gid()); + } else { + let group = Group::from_gid(metadata.gid()).unwrap(); + print!("{}\t", group.name()); + } } print!("{:>5} ", metadata.len()); @@ -234,6 +242,7 @@ struct LsFlags { comma_separate: bool, list: bool, no_owner: bool, + numeric_uid_gid: bool, reverse: bool, size: bool, time: bool, @@ -245,10 +254,16 @@ impl LsFlags { let comma_separate = matches.is_present("comma_separate"); let list = matches.is_present("list"); let no_owner = matches.is_present("no_owner"); + let numeric_uid_gid = matches.is_present("numeric_uid_gid"); let reverse = matches.is_present("reverse"); let size = matches.is_present("size"); let time = matches.is_present("time"); - LsFlags { all, comma_separate, list, no_owner, reverse, size, time } + LsFlags { all, comma_separate, list, no_owner, numeric_uid_gid, reverse, size, time } + } + + /// Whether to print as a list based ont the provided flags + fn show_list(&self) -> bool { + !self.comma_separate && self.list || self.no_owner || self.numeric_uid_gid } } From 93b20248028c27dd26ccc335cef510cdb6c520dc Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Fri, 16 Oct 2020 10:27:38 -0500 Subject: [PATCH 028/133] Ls: run cargo fmt --- ls/src/cli.rs | 24 ++++++------------------ ls/src/main.rs | 2 +- 2 files changed, 7 insertions(+), 19 deletions(-) diff --git a/ls/src/cli.rs b/ls/src/cli.rs index 5eac2cac..0b6b9c78 100644 --- a/ls/src/cli.rs +++ b/ls/src/cli.rs @@ -27,23 +27,15 @@ pub(crate) fn create_app<'a, 'b>() -> App<'a, 'b> { .arg( Arg::with_name("comma_separate") .help("fill width with a comma separated list of entries") - .short("m") - ) - .arg( - Arg::with_name("list") - .help("Use a long listing format") - .short("l"), - ) - .arg( - Arg::with_name("no_owner") - .help("like -l, but do not list owner") - .short("g") + .short("m"), ) + .arg(Arg::with_name("list").help("Use a long listing format").short("l")) + .arg(Arg::with_name("no_owner").help("like -l, but do not list owner").short("g")) .arg( Arg::with_name("numeric_uid_gid") .help("like -l, but list numeric user and group IDs") .short("n") - .long("numeric-uid-gid") + .long("numeric-uid-gid"), ) .arg( Arg::with_name("reverse") @@ -55,11 +47,7 @@ pub(crate) fn create_app<'a, 'b>() -> App<'a, 'b> { Arg::with_name("size") .help("Print the allocated size of each file, in blocks") .short("s") - .long("size") - ) - .arg( - Arg::with_name("time") - .help("Sort by modification time, newest first") - .short("t"), + .long("size"), ) + .arg(Arg::with_name("time").help("Sort by modification time, newest first").short("t")) } diff --git a/ls/src/main.rs b/ls/src/main.rs index 38d1d91c..88d2ac1a 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -3,10 +3,10 @@ use clap::ArgMatches; use coreutils_core::os::group::Group; use coreutils_core::os::passwd::Passwd; -use std::{fs, path, process}; use std::os::unix::fs::{MetadataExt as UnixMetadata, PermissionsExt}; use std::string::String; use std::time::SystemTime; +use std::{fs, path, process}; use ansi_term::Color; From f6262d552546630eaada079e414b724b5ae081f3 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Fri, 16 Oct 2020 16:32:04 -0500 Subject: [PATCH 029/133] Ls: create struct for -l rows --- ls/src/main.rs | 146 ++++++++++++++++++++++++++++++++++++------------- 1 file changed, 109 insertions(+), 37 deletions(-) diff --git a/ls/src/main.rs b/ls/src/main.rs index 88d2ac1a..1c01772e 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -73,10 +73,12 @@ fn print_default(dir: Vec, flags: LsFlags) -> i32 { if flags.comma_separate { print!("{}, ", file_name); } else { - print!("{} ", file_name); + println!("{}", file_name); } } - println!(); + if flags.comma_separate { + println!(); + } exit_code } @@ -85,56 +87,45 @@ fn print_default(dir: Vec, flags: LsFlags) -> i32 { fn print_list(dir: Vec, flags: LsFlags) -> i32 { let mut exit_code = 1; + let mut rows: Vec = Vec::new(); + for entry in dir { match fs::symlink_metadata(entry.path()) { Ok(metadata) => { - let file_name = get_file_name(&entry, flags); - if is_hidden(&entry) && !flags.all { continue; } - if flags.size { - print!("{:>3} ", metadata.blocks()); - } + let list_row = ListRow::from(entry, metadata, flags); - let mode = metadata.permissions().mode(); - let perms = unix_mode::to_string(mode); - print!("{} ", perms); + rows.push(list_row); + } + Err(err) => { + eprintln!("ls: {}", err); + exit_code = 1; + } + } + } - print!("{:>3} ", metadata.nlink()); + for row in rows { + if flags.size { + print!("{:>3} ", row.get_blocks()); + } - if flags.numeric_uid_gid { - print!("{}\t", metadata.uid()); - } else { - let user = Passwd::from_uid(metadata.uid()).unwrap(); - print!("{}\t", user.name()); - } + print!("{} ", row.get_permissions()); - if !flags.no_owner { - if flags.numeric_uid_gid { - print!("{}\t", metadata.gid()); - } else { - let group = Group::from_gid(metadata.gid()).unwrap(); - print!("{}\t", group.name()); - } - } + print!("{:>3} ", row.get_hard_links()); - print!("{:>5} ", metadata.len()); + print!("{}\t", row.get_user()); + print!("{}\t", row.get_group()); - let modified = metadata.modified().unwrap(); - let modified_datetime: DateTime = modified.into(); - print!("{} ", modified_datetime.format("%b %e %k:%M")); + print!("{:>5} ", row.get_size()); - print!("{}", file_name); + print!("{} ", row.get_time()); - println!(); - } - Err(err) => { - eprintln!("ls: {}", err); - exit_code = 1; - } - } + print!("{}", row.get_file_name()); + + println!(); } exit_code @@ -267,3 +258,84 @@ impl LsFlags { !self.comma_separate && self.list || self.no_owner || self.numeric_uid_gid } } + +struct ListRow { + entry: fs::DirEntry, + metadata: fs::Metadata, + flags: LsFlags, +} + +impl ListRow { + fn from(entry: fs::DirEntry, metadata: fs::Metadata, flags: LsFlags) -> Self { + ListRow { entry, metadata, flags } + } + + fn get_blocks(&self) -> String { + let blocks_value = self.metadata.blocks(); + let blocks: String = blocks_value.to_string(); + + blocks + } + + fn get_permissions(&self) -> String { + let mode = self.metadata.permissions().mode(); + + unix_mode::to_string(mode) + } + + fn get_hard_links(&self) -> String { + let hard_links_value = self.metadata.nlink(); + let hard_links: String = hard_links_value.to_string(); + + hard_links + } + + fn get_user(&self) -> String { + let user: String; + + if self.flags.numeric_uid_gid { + let user_value = self.metadata.uid(); + user = user_value.to_string(); + } else { + let uid = Passwd::from_uid(self.metadata.uid()).unwrap(); + let user_value = uid.name(); + user = user_value.to_string(); + } + + user + } + + fn get_group(&self) -> String { + let group: String; + + if self.flags.numeric_uid_gid { + let group_value = self.metadata.gid(); + group = group_value.to_string(); + } else { + let gid = Group::from_gid(self.metadata.gid()).unwrap(); + let group_value = gid.name(); + group = group_value.to_string(); + } + + group + } + + fn get_size(&self) -> String { + let size_value = self.metadata.len(); + let size: String = size_value.to_string(); + + size + } + + fn get_time(&self) -> String { + let modified = self.metadata.modified().unwrap(); + let modified_datetime: DateTime = modified.into(); + let datetime: String = modified_datetime.format("%b %e %k:%M").to_string(); + + datetime + } + + fn get_file_name(&self) -> String { + get_file_name(&self.entry, self.flags) + } +} From 3bbe8f85c209b0b439726c56b8601b0148cd6459 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Fri, 16 Oct 2020 19:41:24 -0500 Subject: [PATCH 030/133] Ls: calculate column padding --- ls/Cargo.toml | 1 + ls/src/main.rs | 54 ++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 47 insertions(+), 8 deletions(-) diff --git a/ls/Cargo.toml b/ls/Cargo.toml index 382d6cb7..1dbc04fc 100644 --- a/ls/Cargo.toml +++ b/ls/Cargo.toml @@ -15,6 +15,7 @@ coreutils_core = { path = "../coreutils_core" } unix_mode = "0.1.1" chrono = "0.4" ansi_term = "0.12.1" +pad = "0.1.6" [build-dependencies] clap = "^2.33.0" diff --git a/ls/src/main.rs b/ls/src/main.rs index 1c01772e..79e10236 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -10,6 +10,8 @@ use std::{fs, path, process}; use ansi_term::Color; +use pad::{PadStr, Alignment}; + extern crate chrono; use chrono::prelude::{DateTime, Utc}; @@ -89,6 +91,12 @@ fn print_list(dir: Vec, flags: LsFlags) -> i32 { let mut rows: Vec = Vec::new(); + let mut block_width = 1; + let mut hard_links_width = 1; + let mut user_width = 1; + let mut group_width = 1; + let mut size_width = 1; + for entry in dir { match fs::symlink_metadata(entry.path()) { Ok(metadata) => { @@ -96,9 +104,39 @@ fn print_list(dir: Vec, flags: LsFlags) -> i32 { continue; } - let list_row = ListRow::from(entry, metadata, flags); + let row = ListRow::from(entry, metadata, flags); + + let block = row.get_blocks().len(); + + if block > block_width { + block_width = block; + } + + let hard_links = row.get_hard_links().len(); + + if hard_links > hard_links_width { + hard_links_width = hard_links; + } + + let user = row.get_user().len(); + + if user > user_width { + user_width = user; + } + + let group = row.get_group().len(); + + if group > group_width { + group_width = group; + } + + let size = row.get_size().len(); + + if size > size_width { + size_width = size; + } - rows.push(list_row); + rows.push(row); } Err(err) => { eprintln!("ls: {}", err); @@ -107,19 +145,19 @@ fn print_list(dir: Vec, flags: LsFlags) -> i32 { } } - for row in rows { + for row in &rows { if flags.size { - print!("{:>3} ", row.get_blocks()); + print!("{} ", row.get_blocks().pad_to_width_with_alignment(block_width, Alignment::Right)); } print!("{} ", row.get_permissions()); - print!("{:>3} ", row.get_hard_links()); + print!("{} ", row.get_hard_links().pad_to_width_with_alignment(hard_links_width, Alignment::Right)); - print!("{}\t", row.get_user()); - print!("{}\t", row.get_group()); + print!("{} ", row.get_user().pad_to_width(user_width)); + print!("{} ", row.get_group().pad_to_width(group_width)); - print!("{:>5} ", row.get_size()); + print!("{:>5} ", row.get_size().pad_to_width_with_alignment(size_width, Alignment::Right)); print!("{} ", row.get_time()); From a5d67c896c720d6ebcb6336d5f9906ee125d4b2d Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Fri, 16 Oct 2020 19:51:07 -0500 Subject: [PATCH 031/133] Ls: fix broken flags --- ls/src/main.rs | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/ls/src/main.rs b/ls/src/main.rs index 79e10236..15834ee4 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -10,7 +10,7 @@ use std::{fs, path, process}; use ansi_term::Color; -use pad::{PadStr, Alignment}; +use pad::{Alignment, PadStr}; extern crate chrono; @@ -106,10 +106,12 @@ fn print_list(dir: Vec, flags: LsFlags) -> i32 { let row = ListRow::from(entry, metadata, flags); - let block = row.get_blocks().len(); + if flags.size { + let block = row.get_blocks().len(); - if block > block_width { - block_width = block; + if block > block_width { + block_width = block; + } } let hard_links = row.get_hard_links().len(); @@ -124,10 +126,12 @@ fn print_list(dir: Vec, flags: LsFlags) -> i32 { user_width = user; } - let group = row.get_group().len(); + if !flags.no_owner { + let group = row.get_group().len(); - if group > group_width { - group_width = group; + if group > group_width { + group_width = group; + } } let size = row.get_size().len(); @@ -147,19 +151,28 @@ fn print_list(dir: Vec, flags: LsFlags) -> i32 { for row in &rows { if flags.size { - print!("{} ", row.get_blocks().pad_to_width_with_alignment(block_width, Alignment::Right)); + print!( + "{} ", + row.get_blocks().pad_to_width_with_alignment(block_width, Alignment::Right) + ); } print!("{} ", row.get_permissions()); - print!("{} ", row.get_hard_links().pad_to_width_with_alignment(hard_links_width, Alignment::Right)); + print!( + "{} ", + row.get_hard_links().pad_to_width_with_alignment(hard_links_width, Alignment::Right) + ); print!("{} ", row.get_user().pad_to_width(user_width)); - print!("{} ", row.get_group().pad_to_width(group_width)); - print!("{:>5} ", row.get_size().pad_to_width_with_alignment(size_width, Alignment::Right)); + if !flags.no_owner { + print!("{} ", row.get_group().pad_to_width(group_width)); + } + + print!("{} ", row.get_size().pad_to_width_with_alignment(size_width, Alignment::Right)); - print!("{} ", row.get_time()); + print!("{} ", row.get_time()); print!("{}", row.get_file_name()); From d66828068a8b564ae3f196c459ed6bc1090e75f1 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Sat, 17 Oct 2020 09:13:01 -0500 Subject: [PATCH 032/133] Ls: add comments --- ls/src/main.rs | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/ls/src/main.rs b/ls/src/main.rs index 15834ee4..fcc789b4 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -89,7 +89,7 @@ fn print_default(dir: Vec, flags: LsFlags) -> i32 { fn print_list(dir: Vec, flags: LsFlags) -> i32 { let mut exit_code = 1; - let mut rows: Vec = Vec::new(); + let mut rows: Vec = Vec::new(); let mut block_width = 1; let mut hard_links_width = 1; @@ -104,7 +104,7 @@ fn print_list(dir: Vec, flags: LsFlags) -> i32 { continue; } - let row = ListRow::from(entry, metadata, flags); + let row = File::from(entry, metadata, flags); if flags.size { let block = row.get_blocks().len(); @@ -310,17 +310,18 @@ impl LsFlags { } } -struct ListRow { +struct File { entry: fs::DirEntry, metadata: fs::Metadata, flags: LsFlags, } -impl ListRow { +impl File { fn from(entry: fs::DirEntry, metadata: fs::Metadata, flags: LsFlags) -> Self { - ListRow { entry, metadata, flags } + File { entry, metadata, flags } } + /// Retrieves the number of blocks allocated to a file as a string fn get_blocks(&self) -> String { let blocks_value = self.metadata.blocks(); let blocks: String = blocks_value.to_string(); @@ -328,12 +329,14 @@ impl ListRow { blocks } + /// Retrieves a files permissions as a string fn get_permissions(&self) -> String { let mode = self.metadata.permissions().mode(); unix_mode::to_string(mode) } + /// Retrieves the number of hard links pointing to a file as a string fn get_hard_links(&self) -> String { let hard_links_value = self.metadata.nlink(); let hard_links: String = hard_links_value.to_string(); @@ -341,6 +344,8 @@ impl ListRow { hard_links } + /// Retrieves the file's user name as a string. If the `-n` flag is set, + /// the the user's ID is returned fn get_user(&self) -> String { let user: String; @@ -356,6 +361,8 @@ impl ListRow { user } + /// Retrieves the file's group name as a string. If the `-n` flag is set, + /// the the group's ID is returned fn get_group(&self) -> String { let group: String; @@ -371,6 +378,7 @@ impl ListRow { group } + /// Retrieve the file's size, in bytes, as a string fn get_size(&self) -> String { let size_value = self.metadata.len(); let size: String = size_value.to_string(); @@ -378,6 +386,7 @@ impl ListRow { size } + /// Retrieves the file's timestamp as a string fn get_time(&self) -> String { let modified = self.metadata.modified().unwrap(); let modified_datetime: DateTime = modified.into(); @@ -386,6 +395,7 @@ impl ListRow { datetime } + /// Retrieves the file's name and any terminal styling as a string fn get_file_name(&self) -> String { get_file_name(&self.entry, self.flags) } From 6000440287bede587ffcceb83e3956675291955e Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Mon, 19 Oct 2020 08:57:35 -0500 Subject: [PATCH 033/133] Ls: get local time --- ls/src/main.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/ls/src/main.rs b/ls/src/main.rs index fcc789b4..8e555f14 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -14,7 +14,7 @@ use pad::{Alignment, PadStr}; extern crate chrono; -use chrono::prelude::{DateTime, Utc}; +use chrono::prelude::{DateTime, Local}; mod cli; @@ -172,7 +172,7 @@ fn print_list(dir: Vec, flags: LsFlags) -> i32 { print!("{} ", row.get_size().pad_to_width_with_alignment(size_width, Alignment::Right)); - print!("{} ", row.get_time()); + print!("{} ", row.get_time()); print!("{}", row.get_file_name()); @@ -389,10 +389,9 @@ impl File { /// Retrieves the file's timestamp as a string fn get_time(&self) -> String { let modified = self.metadata.modified().unwrap(); - let modified_datetime: DateTime = modified.into(); - let datetime: String = modified_datetime.format("%b %e %k:%M").to_string(); + let modified_datetime: DateTime = modified.into(); - datetime + modified_datetime.format("%b %e %k:%M").to_string() } /// Retrieves the file's name and any terminal styling as a string From 2495277d53234abf320df2d4a7935be014c54470 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Mon, 19 Oct 2020 10:02:26 -0500 Subject: [PATCH 034/133] Ls: add -F flag --- ls/src/cli.rs | 6 ++++++ ls/src/main.rs | 46 +++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/ls/src/cli.rs b/ls/src/cli.rs index 0b6b9c78..2ed1d586 100644 --- a/ls/src/cli.rs +++ b/ls/src/cli.rs @@ -24,6 +24,12 @@ pub(crate) fn create_app<'a, 'b>() -> App<'a, 'b> { .short("a") .long("all"), ) + .arg( + Arg::with_name("classify") + .help("append indicator (one of */=>@|) to entries") + .short("F") + .long("classify"), + ) .arg( Arg::with_name("comma_separate") .help("fill width with a comma separated list of entries") diff --git a/ls/src/main.rs b/ls/src/main.rs index 8e555f14..0714ec7f 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -3,7 +3,7 @@ use clap::ArgMatches; use coreutils_core::os::group::Group; use coreutils_core::os::passwd::Passwd; -use std::os::unix::fs::{MetadataExt as UnixMetadata, PermissionsExt}; +use std::os::unix::fs::{FileTypeExt, MetadataExt, PermissionsExt}; use std::string::String; use std::time::SystemTime; use std::{fs, path, process}; @@ -191,12 +191,20 @@ fn get_file_name(file: &fs::DirEntry, flags: LsFlags) -> String { if let Ok(metadata) = metadata { if is_executable(&file.path()) { file_name = add_executable_color(file_name); + + if flags.classify { + file_name = format!("{}*", file_name); + } } if metadata.file_type().is_symlink() { file_name = add_symlink_color(file_name); - if flags.list || flags.no_owner { + if flags.classify && !flags.show_list() { + file_name = format!("{}@", file_name); + } + + if flags.show_list() { let symlink = fs::read_link(file.path()); if let Ok(symlink) = symlink { @@ -204,6 +212,10 @@ fn get_file_name(file: &fs::DirEntry, flags: LsFlags) -> String { if is_executable(&symlink) { symlink_name = add_executable_color(symlink_name); + + if flags.classify { + symlink_name = format!("{}*", symlink_name); + } } file_name = format!("{} -> {}", file_name, symlink_name); @@ -211,8 +223,20 @@ fn get_file_name(file: &fs::DirEntry, flags: LsFlags) -> String { } } + if metadata.file_type().is_fifo() { + file_name = add_named_pipe_color(file_name); + + if flags.classify { + file_name = format!("{}|", file_name); + } + } + if metadata.is_dir() { file_name = add_directory_color(file_name); + + if flags.classify { + file_name = format!("{}/", file_name); + } } } @@ -229,6 +253,10 @@ fn add_directory_color(directory_name: String) -> String { Color::Blue.bold().paint(directory_name).to_string() } +fn add_named_pipe_color(named_pipe_name: String) -> String { + Color::Yellow.on(Color::Black).paint(named_pipe_name).to_string() +} + /// Adds a bold cyan color to a file name to represent a symlink fn add_symlink_color(symlink_name: String) -> String { Color::Cyan.bold().paint(symlink_name).to_string() @@ -281,6 +309,7 @@ fn sort_by_time(dir: &fs::DirEntry) -> SystemTime { #[derive(Default, Copy, Clone)] struct LsFlags { all: bool, + classify: bool, comma_separate: bool, list: bool, no_owner: bool, @@ -293,6 +322,7 @@ struct LsFlags { impl LsFlags { fn from_matches(matches: &ArgMatches<'_>) -> Self { let all = matches.is_present("all"); + let classify = matches.is_present("classify"); let comma_separate = matches.is_present("comma_separate"); let list = matches.is_present("list"); let no_owner = matches.is_present("no_owner"); @@ -301,7 +331,17 @@ impl LsFlags { let size = matches.is_present("size"); let time = matches.is_present("time"); - LsFlags { all, comma_separate, list, no_owner, numeric_uid_gid, reverse, size, time } + LsFlags { + all, + classify, + comma_separate, + list, + no_owner, + numeric_uid_gid, + reverse, + size, + time, + } } /// Whether to print as a list based ont the provided flags From 325347aa1232ba2b8581664b3b6e542bc89cfa23 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Mon, 19 Oct 2020 11:11:35 -0500 Subject: [PATCH 035/133] Ls: split into separate modules --- ls/src/file.rs | 206 ++++++++++++++++++++++++++++++++++++ ls/src/flags.rs | 45 ++++++++ ls/src/main.rs | 269 +++--------------------------------------------- 3 files changed, 265 insertions(+), 255 deletions(-) create mode 100644 ls/src/file.rs create mode 100644 ls/src/flags.rs diff --git a/ls/src/file.rs b/ls/src/file.rs new file mode 100644 index 00000000..319db835 --- /dev/null +++ b/ls/src/file.rs @@ -0,0 +1,206 @@ +use coreutils_core::os::group::Group; +use coreutils_core::os::passwd::Passwd; + +use std::os::unix::fs::{FileTypeExt, MetadataExt, PermissionsExt}; +use std::string::String; +use std::{fs, path}; + +use ansi_term::Color; + +extern crate chrono; + +use chrono::prelude::{DateTime, Local}; + +use crate::flags::LsFlags; + +pub(crate) struct File { + entry: fs::DirEntry, + metadata: fs::Metadata, + flags: LsFlags, +} + +impl File { + pub fn from(entry: fs::DirEntry, metadata: fs::Metadata, flags: LsFlags) -> Self { + File { entry, metadata, flags } + } + + /// Retrieves the number of blocks allocated to a file as a string + pub fn get_blocks(&self) -> String { + let blocks_value = self.metadata.blocks(); + let blocks: String = blocks_value.to_string(); + + blocks + } + + /// Retrieves a files permissions as a string + pub fn get_permissions(&self) -> String { + let mode = self.metadata.permissions().mode(); + + unix_mode::to_string(mode) + } + + /// Retrieves the number of hard links pointing to a file as a string + pub fn get_hard_links(&self) -> String { + let hard_links_value = self.metadata.nlink(); + let hard_links: String = hard_links_value.to_string(); + + hard_links + } + + /// Retrieves the file's user name as a string. If the `-n` flag is set, + /// the the user's ID is returned + pub fn get_user(&self) -> String { + let user: String; + + if self.flags.numeric_uid_gid { + let user_value = self.metadata.uid(); + user = user_value.to_string(); + } else { + let uid = Passwd::from_uid(self.metadata.uid()).unwrap(); + let user_value = uid.name(); + user = user_value.to_string(); + } + + user + } + + /// Retrieves the file's group name as a string. If the `-n` flag is set, + /// the the group's ID is returned + pub fn get_group(&self) -> String { + let group: String; + + if self.flags.numeric_uid_gid { + let group_value = self.metadata.gid(); + group = group_value.to_string(); + } else { + let gid = Group::from_gid(self.metadata.gid()).unwrap(); + let group_value = gid.name(); + group = group_value.to_string(); + } + + group + } + + /// Retrieve the file's size, in bytes, as a string + pub fn get_size(&self) -> String { + let size_value = self.metadata.len(); + let size: String = size_value.to_string(); + + size + } + + /// Retrieves the file's timestamp as a string + pub fn get_time(&self) -> String { + let modified = self.metadata.modified().unwrap(); + let modified_datetime: DateTime = modified.into(); + + modified_datetime.format("%b %e %k:%M").to_string() + } + + /// Check if a path is an executable file + pub fn is_executable(path: &path::PathBuf) -> bool { + let mut result = false; + + let metadata = fs::symlink_metadata(path); + + if let Ok(metadata) = metadata { + result = metadata.is_file() && metadata.permissions().mode() & 0o111 != 0; + } + + result + } + + pub fn is_hidden(entry: &fs::DirEntry) -> bool { + let mut result = false; + + let file_name = entry.file_name().into_string(); + + if let Ok(file_name) = file_name { + result = file_name.starts_with('.') + } + + result + } + + /// Gets a file name from a directory entry and adds appropriate formatting + pub fn get_file_name(&self) -> String { + let mut file_name = self.entry.file_name().into_string().unwrap(); + + let flags = self.flags; + + let metadata = fs::symlink_metadata(self.entry.path()); + + if let Ok(metadata) = metadata { + if File::is_executable(&self.entry.path()) { + file_name = self.add_executable_color(file_name); + + if flags.classify { + file_name = format!("{}*", file_name); + } + } + + if metadata.file_type().is_symlink() { + file_name = self.add_symlink_color(file_name); + + if flags.classify && !flags.show_list() { + file_name = format!("{}@", file_name); + } + + if flags.show_list() { + let symlink = fs::read_link(self.entry.path()); + + if let Ok(symlink) = symlink { + let mut symlink_name = String::from(symlink.to_str().unwrap()); + + if File::is_executable(&symlink) { + symlink_name = self.add_executable_color(symlink_name); + + if flags.classify { + symlink_name = format!("{}*", symlink_name); + } + } + + file_name = format!("{} -> {}", file_name, symlink_name); + } + } + } + + if metadata.file_type().is_fifo() { + file_name = self.add_named_pipe_color(file_name); + + if flags.classify { + file_name = format!("{}|", file_name); + } + } + + if metadata.is_dir() { + file_name = self.add_directory_color(file_name); + + if flags.classify { + file_name = format!("{}/", file_name); + } + } + } + + file_name + } + + /// Adds a bold green color to a file name to represent an executable + pub fn add_executable_color(&self, file_name: String) -> String { + Color::Green.bold().paint(file_name).to_string() + } + + /// Adds a bold blue color to a directory name + pub fn add_directory_color(&self, directory_name: String) -> String { + Color::Blue.bold().paint(directory_name).to_string() + } + + pub fn add_named_pipe_color(&self, named_pipe_name: String) -> String { + Color::Yellow.on(Color::Black).paint(named_pipe_name).to_string() + } + + /// Adds a bold cyan color to a file name to represent a symlink + pub fn add_symlink_color(&self, symlink_name: String) -> String { + Color::Cyan.bold().paint(symlink_name).to_string() + } +} diff --git a/ls/src/flags.rs b/ls/src/flags.rs new file mode 100644 index 00000000..f4f817af --- /dev/null +++ b/ls/src/flags.rs @@ -0,0 +1,45 @@ +use clap::ArgMatches; + +#[derive(Default, Copy, Clone)] +pub(crate) struct LsFlags { + pub all: bool, + pub classify: bool, + pub comma_separate: bool, + pub list: bool, + pub no_owner: bool, + pub numeric_uid_gid: bool, + pub reverse: bool, + pub size: bool, + pub time: bool, +} + +impl LsFlags { + pub fn from_matches(matches: &ArgMatches<'_>) -> Self { + let all = matches.is_present("all"); + let classify = matches.is_present("classify"); + let comma_separate = matches.is_present("comma_separate"); + let list = matches.is_present("list"); + let no_owner = matches.is_present("no_owner"); + let numeric_uid_gid = matches.is_present("numeric_uid_gid"); + let reverse = matches.is_present("reverse"); + let size = matches.is_present("size"); + let time = matches.is_present("time"); + + LsFlags { + all, + classify, + comma_separate, + list, + no_owner, + numeric_uid_gid, + reverse, + size, + time, + } + } + + /// Whether to print as a list based ont the provided flags + pub fn show_list(&self) -> bool { + !self.comma_separate && self.list || self.no_owner || self.numeric_uid_gid + } +} diff --git a/ls/src/main.rs b/ls/src/main.rs index 0714ec7f..832f10ea 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -1,22 +1,17 @@ -use clap::ArgMatches; - -use coreutils_core::os::group::Group; -use coreutils_core::os::passwd::Passwd; - -use std::os::unix::fs::{FileTypeExt, MetadataExt, PermissionsExt}; use std::string::String; use std::time::SystemTime; -use std::{fs, path, process}; - -use ansi_term::Color; +use std::{fs, process}; use pad::{Alignment, PadStr}; extern crate chrono; -use chrono::prelude::{DateTime, Local}; - mod cli; +mod file; +mod flags; + +use file::File; +use flags::LsFlags; fn main() { let matches = cli::create_app().get_matches(); @@ -66,12 +61,16 @@ fn print_default(dir: Vec, flags: LsFlags) -> i32 { let exit_code = 1; for entry in dir { - let file_name = get_file_name(&entry, flags); - - if is_hidden(&entry) && !flags.all { + if File::is_hidden(&entry) && !flags.all { continue; } + let path = entry.path(); + + let file = File::from(entry, fs::symlink_metadata(path).unwrap(), flags); + + let file_name = file.get_file_name(); + if flags.comma_separate { print!("{}, ", file_name); } else { @@ -100,7 +99,7 @@ fn print_list(dir: Vec, flags: LsFlags) -> i32 { for entry in dir { match fs::symlink_metadata(entry.path()) { Ok(metadata) => { - if is_hidden(&entry) && !flags.all { + if File::is_hidden(&entry) && !flags.all { continue; } @@ -182,112 +181,6 @@ fn print_list(dir: Vec, flags: LsFlags) -> i32 { exit_code } -/// Gets a file name from a directory entry and adds appropriate formatting -fn get_file_name(file: &fs::DirEntry, flags: LsFlags) -> String { - let mut file_name = file.file_name().into_string().unwrap(); - - let metadata = fs::symlink_metadata(file.path()); - - if let Ok(metadata) = metadata { - if is_executable(&file.path()) { - file_name = add_executable_color(file_name); - - if flags.classify { - file_name = format!("{}*", file_name); - } - } - - if metadata.file_type().is_symlink() { - file_name = add_symlink_color(file_name); - - if flags.classify && !flags.show_list() { - file_name = format!("{}@", file_name); - } - - if flags.show_list() { - let symlink = fs::read_link(file.path()); - - if let Ok(symlink) = symlink { - let mut symlink_name = String::from(symlink.to_str().unwrap()); - - if is_executable(&symlink) { - symlink_name = add_executable_color(symlink_name); - - if flags.classify { - symlink_name = format!("{}*", symlink_name); - } - } - - file_name = format!("{} -> {}", file_name, symlink_name); - } - } - } - - if metadata.file_type().is_fifo() { - file_name = add_named_pipe_color(file_name); - - if flags.classify { - file_name = format!("{}|", file_name); - } - } - - if metadata.is_dir() { - file_name = add_directory_color(file_name); - - if flags.classify { - file_name = format!("{}/", file_name); - } - } - } - - file_name -} - -/// Adds a bold green color to a file name to represent an executable -fn add_executable_color(file_name: String) -> String { - Color::Green.bold().paint(file_name).to_string() -} - -/// Adds a bold blue color to a directory name -fn add_directory_color(directory_name: String) -> String { - Color::Blue.bold().paint(directory_name).to_string() -} - -fn add_named_pipe_color(named_pipe_name: String) -> String { - Color::Yellow.on(Color::Black).paint(named_pipe_name).to_string() -} - -/// Adds a bold cyan color to a file name to represent a symlink -fn add_symlink_color(symlink_name: String) -> String { - Color::Cyan.bold().paint(symlink_name).to_string() -} - -/// Check if a path is an executable file -fn is_executable(path: &path::PathBuf) -> bool { - let mut result = false; - - let metadata = fs::symlink_metadata(path); - - if let Ok(metadata) = metadata { - result = metadata.is_file() && metadata.permissions().mode() & 0o111 != 0; - } - - result -} - -/// Checks if a string looks like a hidden unix file -fn is_hidden(entry: &fs::DirEntry) -> bool { - let mut result = false; - - let file_name = entry.file_name().into_string(); - - if let Ok(file_name) = file_name { - result = file_name.starts_with('.') - } - - result -} - /// Sort a list of directories by file name alphabetically fn sort_by_name(dir: &fs::DirEntry) -> String { let file_name = dir.file_name().into_string().unwrap(); @@ -305,137 +198,3 @@ fn sort_by_time(dir: &fs::DirEntry) -> SystemTime { SystemTime::now() } } - -#[derive(Default, Copy, Clone)] -struct LsFlags { - all: bool, - classify: bool, - comma_separate: bool, - list: bool, - no_owner: bool, - numeric_uid_gid: bool, - reverse: bool, - size: bool, - time: bool, -} - -impl LsFlags { - fn from_matches(matches: &ArgMatches<'_>) -> Self { - let all = matches.is_present("all"); - let classify = matches.is_present("classify"); - let comma_separate = matches.is_present("comma_separate"); - let list = matches.is_present("list"); - let no_owner = matches.is_present("no_owner"); - let numeric_uid_gid = matches.is_present("numeric_uid_gid"); - let reverse = matches.is_present("reverse"); - let size = matches.is_present("size"); - let time = matches.is_present("time"); - - LsFlags { - all, - classify, - comma_separate, - list, - no_owner, - numeric_uid_gid, - reverse, - size, - time, - } - } - - /// Whether to print as a list based ont the provided flags - fn show_list(&self) -> bool { - !self.comma_separate && self.list || self.no_owner || self.numeric_uid_gid - } -} - -struct File { - entry: fs::DirEntry, - metadata: fs::Metadata, - flags: LsFlags, -} - -impl File { - fn from(entry: fs::DirEntry, metadata: fs::Metadata, flags: LsFlags) -> Self { - File { entry, metadata, flags } - } - - /// Retrieves the number of blocks allocated to a file as a string - fn get_blocks(&self) -> String { - let blocks_value = self.metadata.blocks(); - let blocks: String = blocks_value.to_string(); - - blocks - } - - /// Retrieves a files permissions as a string - fn get_permissions(&self) -> String { - let mode = self.metadata.permissions().mode(); - - unix_mode::to_string(mode) - } - - /// Retrieves the number of hard links pointing to a file as a string - fn get_hard_links(&self) -> String { - let hard_links_value = self.metadata.nlink(); - let hard_links: String = hard_links_value.to_string(); - - hard_links - } - - /// Retrieves the file's user name as a string. If the `-n` flag is set, - /// the the user's ID is returned - fn get_user(&self) -> String { - let user: String; - - if self.flags.numeric_uid_gid { - let user_value = self.metadata.uid(); - user = user_value.to_string(); - } else { - let uid = Passwd::from_uid(self.metadata.uid()).unwrap(); - let user_value = uid.name(); - user = user_value.to_string(); - } - - user - } - - /// Retrieves the file's group name as a string. If the `-n` flag is set, - /// the the group's ID is returned - fn get_group(&self) -> String { - let group: String; - - if self.flags.numeric_uid_gid { - let group_value = self.metadata.gid(); - group = group_value.to_string(); - } else { - let gid = Group::from_gid(self.metadata.gid()).unwrap(); - let group_value = gid.name(); - group = group_value.to_string(); - } - - group - } - - /// Retrieve the file's size, in bytes, as a string - fn get_size(&self) -> String { - let size_value = self.metadata.len(); - let size: String = size_value.to_string(); - - size - } - - /// Retrieves the file's timestamp as a string - fn get_time(&self) -> String { - let modified = self.metadata.modified().unwrap(); - let modified_datetime: DateTime = modified.into(); - - modified_datetime.format("%b %e %k:%M").to_string() - } - - /// Retrieves the file's name and any terminal styling as a string - fn get_file_name(&self) -> String { - get_file_name(&self.entry, self.flags) - } -} From 1bb0f3170e54a8bea3e8e866650173c2f0c18de4 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Mon, 19 Oct 2020 13:26:01 -0500 Subject: [PATCH 036/133] Ls: capitilize help messages --- ls/src/cli.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ls/src/cli.rs b/ls/src/cli.rs index 2ed1d586..fffe0b13 100644 --- a/ls/src/cli.rs +++ b/ls/src/cli.rs @@ -26,20 +26,20 @@ pub(crate) fn create_app<'a, 'b>() -> App<'a, 'b> { ) .arg( Arg::with_name("classify") - .help("append indicator (one of */=>@|) to entries") + .help("Append indicator (one of */=>@|) to entries") .short("F") .long("classify"), ) .arg( Arg::with_name("comma_separate") - .help("fill width with a comma separated list of entries") + .help("Fill width with a comma separated list of entries") .short("m"), ) .arg(Arg::with_name("list").help("Use a long listing format").short("l")) - .arg(Arg::with_name("no_owner").help("like -l, but do not list owner").short("g")) + .arg(Arg::with_name("no_owner").help("Like -l, but do not list owner").short("g")) .arg( Arg::with_name("numeric_uid_gid") - .help("like -l, but list numeric user and group IDs") + .help("Like -l, but list numeric user and group IDs") .short("n") .long("numeric-uid-gid"), ) From 17670cd14151106c6b1b55c23b57bc393b58b772 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Mon, 19 Oct 2020 13:35:51 -0500 Subject: [PATCH 037/133] Ls: add -S flag --- ls/src/cli.rs | 5 +++++ ls/src/flags.rs | 3 +++ ls/src/main.rs | 16 ++++++++++++++++ 3 files changed, 24 insertions(+) diff --git a/ls/src/cli.rs b/ls/src/cli.rs index fffe0b13..d4d6dc16 100644 --- a/ls/src/cli.rs +++ b/ls/src/cli.rs @@ -55,5 +55,10 @@ pub(crate) fn create_app<'a, 'b>() -> App<'a, 'b> { .short("s") .long("size"), ) + .arg( + Arg::with_name("sort_size") + .help("Sort by first file size, largest first") + .short("S"), + ) .arg(Arg::with_name("time").help("Sort by modification time, newest first").short("t")) } diff --git a/ls/src/flags.rs b/ls/src/flags.rs index f4f817af..78ad8fae 100644 --- a/ls/src/flags.rs +++ b/ls/src/flags.rs @@ -10,6 +10,7 @@ pub(crate) struct LsFlags { pub numeric_uid_gid: bool, pub reverse: bool, pub size: bool, + pub sort_size: bool, pub time: bool, } @@ -23,6 +24,7 @@ impl LsFlags { let numeric_uid_gid = matches.is_present("numeric_uid_gid"); let reverse = matches.is_present("reverse"); let size = matches.is_present("size"); + let sort_size = matches.is_present("sort_size"); let time = matches.is_present("time"); LsFlags { @@ -34,6 +36,7 @@ impl LsFlags { numeric_uid_gid, reverse, size, + sort_size, time, } } diff --git a/ls/src/main.rs b/ls/src/main.rs index 832f10ea..0146b2f2 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -29,6 +29,9 @@ fn main() { if flags.time { dir.sort_by_key(sort_by_time); dir.reverse(); + } else if flags.sort_size { + dir.sort_by_key(sort_by_size); + dir.reverse(); } else { // Sort the directory entries by file name by default dir.sort_by_key(sort_by_name); @@ -188,6 +191,19 @@ fn sort_by_name(dir: &fs::DirEntry) -> String { file_name.to_lowercase() } +/// Sort a list of directories by size +fn sort_by_size(dir: &fs::DirEntry) -> u64 { + let metadata = dir.metadata(); + + if let Ok(metadata) = metadata { + metadata.len() + } else { + let default_size: u64 = 0 as u64; + + default_size + } +} + /// Sort a list of directories by modification time fn sort_by_time(dir: &fs::DirEntry) -> SystemTime { let metadata = fs::metadata(dir.path()); From 2998c948d01d9bd377fe5bfc88006b17a4539b52 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Mon, 19 Oct 2020 13:36:45 -0500 Subject: [PATCH 038/133] Ls: update ls progress in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f61c0a21..eb3f20e6 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ cargo install --path . | link | | | X | | ln | X | | | | logname | | | X | -| ls | X | | | +| ls | | X | | | mkdir | | | X | | mktemp | | | X | | mkfifo | X | | | From 89d80433f2d26bbefd00830525db9e9d605703a8 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Mon, 19 Oct 2020 13:38:53 -0500 Subject: [PATCH 039/133] Ls: remove unnecessary code --- ls/src/file.rs | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/ls/src/file.rs b/ls/src/file.rs index 319db835..3b3bf965 100644 --- a/ls/src/file.rs +++ b/ls/src/file.rs @@ -26,10 +26,7 @@ impl File { /// Retrieves the number of blocks allocated to a file as a string pub fn get_blocks(&self) -> String { - let blocks_value = self.metadata.blocks(); - let blocks: String = blocks_value.to_string(); - - blocks + self.metadata.blocks().to_string() } /// Retrieves a files permissions as a string @@ -41,10 +38,7 @@ impl File { /// Retrieves the number of hard links pointing to a file as a string pub fn get_hard_links(&self) -> String { - let hard_links_value = self.metadata.nlink(); - let hard_links: String = hard_links_value.to_string(); - - hard_links + self.metadata.nlink().to_string() } /// Retrieves the file's user name as a string. If the `-n` flag is set, @@ -83,10 +77,7 @@ impl File { /// Retrieve the file's size, in bytes, as a string pub fn get_size(&self) -> String { - let size_value = self.metadata.len(); - let size: String = size_value.to_string(); - - size + self.metadata.len().to_string() } /// Retrieves the file's timestamp as a string From 9a327c3c0c10fd6bf161827841a7c878266d1ecf Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Mon, 19 Oct 2020 14:13:51 -0500 Subject: [PATCH 040/133] Ls: add -u flag --- ls/src/cli.rs | 5 +++++ ls/src/file.rs | 13 ++++++++++--- ls/src/flags.rs | 3 +++ ls/src/main.rs | 19 +++++++++++++++++-- 4 files changed, 35 insertions(+), 5 deletions(-) diff --git a/ls/src/cli.rs b/ls/src/cli.rs index d4d6dc16..47c61e15 100644 --- a/ls/src/cli.rs +++ b/ls/src/cli.rs @@ -35,6 +35,11 @@ pub(crate) fn create_app<'a, 'b>() -> App<'a, 'b> { .help("Fill width with a comma separated list of entries") .short("m"), ) + .arg( + Arg::with_name("last_accessed") + .help("Use time of last access instead of last modification of the file for sorting -t or writing -l") + .short("u"), + ) .arg(Arg::with_name("list").help("Use a long listing format").short("l")) .arg(Arg::with_name("no_owner").help("Like -l, but do not list owner").short("g")) .arg( diff --git a/ls/src/file.rs b/ls/src/file.rs index 3b3bf965..bfcf41ca 100644 --- a/ls/src/file.rs +++ b/ls/src/file.rs @@ -82,10 +82,17 @@ impl File { /// Retrieves the file's timestamp as a string pub fn get_time(&self) -> String { - let modified = self.metadata.modified().unwrap(); - let modified_datetime: DateTime = modified.into(); + let datetime: DateTime; - modified_datetime.format("%b %e %k:%M").to_string() + if self.flags.last_accessed { + let accessed = self.metadata.accessed().unwrap(); + datetime = accessed.into(); + } else { + let modified = self.metadata.modified().unwrap(); + datetime = modified.into(); + } + + datetime.format("%b %e %k:%M").to_string() } /// Check if a path is an executable file diff --git a/ls/src/flags.rs b/ls/src/flags.rs index 78ad8fae..6ba3de28 100644 --- a/ls/src/flags.rs +++ b/ls/src/flags.rs @@ -5,6 +5,7 @@ pub(crate) struct LsFlags { pub all: bool, pub classify: bool, pub comma_separate: bool, + pub last_accessed: bool, pub list: bool, pub no_owner: bool, pub numeric_uid_gid: bool, @@ -19,6 +20,7 @@ impl LsFlags { let all = matches.is_present("all"); let classify = matches.is_present("classify"); let comma_separate = matches.is_present("comma_separate"); + let last_accessed = matches.is_present("last_accessed"); let list = matches.is_present("list"); let no_owner = matches.is_present("no_owner"); let numeric_uid_gid = matches.is_present("numeric_uid_gid"); @@ -31,6 +33,7 @@ impl LsFlags { all, classify, comma_separate, + last_accessed, list, no_owner, numeric_uid_gid, diff --git a/ls/src/main.rs b/ls/src/main.rs index 0146b2f2..7e67a09c 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -26,8 +26,12 @@ fn main() { Ok(dir) => { let mut dir: Vec<_> = dir.map(|r| r.unwrap()).collect(); - if flags.time { - dir.sort_by_key(sort_by_time); + if flags.time { + if flags.last_accessed { + dir.sort_by_key(sort_by_access_time); + } else { + dir.sort_by_key(sort_by_time); + } dir.reverse(); } else if flags.sort_size { dir.sort_by_key(sort_by_size); @@ -184,6 +188,17 @@ fn print_list(dir: Vec, flags: LsFlags) -> i32 { exit_code } +/// Sort a list of directories by last accessed time +fn sort_by_access_time(dir: &fs::DirEntry) -> SystemTime { + let metadata = dir.metadata(); + + if let Ok(metadata) = metadata { + metadata.accessed().unwrap() + } else { + SystemTime::now() + } +} + /// Sort a list of directories by file name alphabetically fn sort_by_name(dir: &fs::DirEntry) -> String { let file_name = dir.file_name().into_string().unwrap(); From 55a2e10842244369fbafadedecd93a3864a5f46d Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Mon, 19 Oct 2020 14:14:27 -0500 Subject: [PATCH 041/133] Ls: remove excess space --- ls/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ls/src/main.rs b/ls/src/main.rs index 7e67a09c..d095b742 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -26,7 +26,7 @@ fn main() { Ok(dir) => { let mut dir: Vec<_> = dir.map(|r| r.unwrap()).collect(); - if flags.time { + if flags.time { if flags.last_accessed { dir.sort_by_key(sort_by_access_time); } else { @@ -193,7 +193,7 @@ fn sort_by_access_time(dir: &fs::DirEntry) -> SystemTime { let metadata = dir.metadata(); if let Ok(metadata) = metadata { - metadata.accessed().unwrap() + metadata.accessed().unwrap() } else { SystemTime::now() } From c16966026763a425a9db9b3a494d6b32cd9feaec Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Mon, 19 Oct 2020 15:24:59 -0500 Subject: [PATCH 042/133] Ls: add -p flag --- ls/src/cli.rs | 5 +++++ ls/src/file.rs | 2 +- ls/src/flags.rs | 3 +++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/ls/src/cli.rs b/ls/src/cli.rs index 47c61e15..666fba90 100644 --- a/ls/src/cli.rs +++ b/ls/src/cli.rs @@ -35,6 +35,11 @@ pub(crate) fn create_app<'a, 'b>() -> App<'a, 'b> { .help("Fill width with a comma separated list of entries") .short("m"), ) + .arg( + Arg::with_name("indicator") + .help("Write a ( '/' ) after each filename if that file is a directory") + .short("p"), + ) .arg( Arg::with_name("last_accessed") .help("Use time of last access instead of last modification of the file for sorting -t or writing -l") diff --git a/ls/src/file.rs b/ls/src/file.rs index bfcf41ca..279f3d0d 100644 --- a/ls/src/file.rs +++ b/ls/src/file.rs @@ -174,7 +174,7 @@ impl File { if metadata.is_dir() { file_name = self.add_directory_color(file_name); - if flags.classify { + if flags.classify || flags.indicator { file_name = format!("{}/", file_name); } } diff --git a/ls/src/flags.rs b/ls/src/flags.rs index 6ba3de28..3dba5809 100644 --- a/ls/src/flags.rs +++ b/ls/src/flags.rs @@ -5,6 +5,7 @@ pub(crate) struct LsFlags { pub all: bool, pub classify: bool, pub comma_separate: bool, + pub indicator: bool, pub last_accessed: bool, pub list: bool, pub no_owner: bool, @@ -20,6 +21,7 @@ impl LsFlags { let all = matches.is_present("all"); let classify = matches.is_present("classify"); let comma_separate = matches.is_present("comma_separate"); + let indicator = matches.is_present("indicator"); let last_accessed = matches.is_present("last_accessed"); let list = matches.is_present("list"); let no_owner = matches.is_present("no_owner"); @@ -33,6 +35,7 @@ impl LsFlags { all, classify, comma_separate, + indicator, last_accessed, list, no_owner, From 1f47cb880fec766d2a2ba58fa9dad05e2d3b6412 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Tue, 20 Oct 2020 09:52:07 -0500 Subject: [PATCH 043/133] Ls: try to reduce complexity --- ls/src/file.rs | 94 +++++++++++++++---------------- ls/src/main.rs | 148 +++++++++++++++++++------------------------------ 2 files changed, 100 insertions(+), 142 deletions(-) diff --git a/ls/src/file.rs b/ls/src/file.rs index 279f3d0d..5964f6f8 100644 --- a/ls/src/file.rs +++ b/ls/src/file.rs @@ -3,7 +3,7 @@ use coreutils_core::os::passwd::Passwd; use std::os::unix::fs::{FileTypeExt, MetadataExt, PermissionsExt}; use std::string::String; -use std::{fs, path}; +use std::{fs, io, path}; use ansi_term::Color; @@ -14,14 +14,20 @@ use chrono::prelude::{DateTime, Local}; use crate::flags::LsFlags; pub(crate) struct File { - entry: fs::DirEntry, - metadata: fs::Metadata, + pub name: String, + pub path: path::PathBuf, + pub metadata: fs::Metadata, flags: LsFlags, } impl File { - pub fn from(entry: fs::DirEntry, metadata: fs::Metadata, flags: LsFlags) -> Self { - File { entry, metadata, flags } + pub fn from(entry: fs::DirEntry, flags: LsFlags) -> io::Result { + let path = entry.path(); + let metadata = fs::symlink_metadata(path.clone()).expect("Failed to read metadata"); + + let name = entry.file_name().into_string().expect("Failed to get file name as string"); + + Ok(File { name, path, metadata, flags }) } /// Retrieves the number of blocks allocated to a file as a string @@ -108,75 +114,63 @@ impl File { result } - pub fn is_hidden(entry: &fs::DirEntry) -> bool { - let mut result = false; - - let file_name = entry.file_name().into_string(); - - if let Ok(file_name) = file_name { - result = file_name.starts_with('.') - } - - result + pub fn is_hidden(name: &String) -> bool { + name.starts_with('.') } /// Gets a file name from a directory entry and adds appropriate formatting pub fn get_file_name(&self) -> String { - let mut file_name = self.entry.file_name().into_string().unwrap(); + let mut file_name = self.name.clone(); let flags = self.flags; - let metadata = fs::symlink_metadata(self.entry.path()); - - if let Ok(metadata) = metadata { - if File::is_executable(&self.entry.path()) { - file_name = self.add_executable_color(file_name); + if File::is_executable(&self.path) { + file_name = self.add_executable_color(file_name); - if flags.classify { - file_name = format!("{}*", file_name); - } + if flags.classify { + file_name = format!("{}*", file_name); } + } - if metadata.file_type().is_symlink() { - file_name = self.add_symlink_color(file_name); + if self.metadata.file_type().is_symlink() { + file_name = self.add_symlink_color(file_name); - if flags.classify && !flags.show_list() { - file_name = format!("{}@", file_name); - } + if flags.classify && !flags.show_list() { + file_name = format!("{}@", file_name); + } - if flags.show_list() { - let symlink = fs::read_link(self.entry.path()); + if flags.show_list() { + let symlink = fs::read_link(self.path.clone()); - if let Ok(symlink) = symlink { - let mut symlink_name = String::from(symlink.to_str().unwrap()); + if let Ok(symlink) = symlink { + let mut symlink_name = String::from(symlink.to_str().unwrap()); - if File::is_executable(&symlink) { - symlink_name = self.add_executable_color(symlink_name); + if File::is_executable(&symlink) { + symlink_name = self.add_executable_color(symlink_name); - if flags.classify { - symlink_name = format!("{}*", symlink_name); - } + if flags.classify { + symlink_name = format!("{}*", symlink_name); } - - file_name = format!("{} -> {}", file_name, symlink_name); } + + file_name = format!("{} -> {}", file_name, symlink_name); } } + } - if metadata.file_type().is_fifo() { - file_name = self.add_named_pipe_color(file_name); + if self.metadata.file_type().is_fifo() { + file_name = self.add_named_pipe_color(file_name); - if flags.classify { - file_name = format!("{}|", file_name); - } + if flags.classify { + file_name = format!("{}|", file_name); } + } - if metadata.is_dir() { - file_name = self.add_directory_color(file_name); + if self.metadata.is_dir() { + file_name = self.add_directory_color(file_name); - if flags.classify || flags.indicator { - file_name = format!("{}/", file_name); - } + if flags.classify || flags.indicator { + file_name = format!("{}/", file_name); } } diff --git a/ls/src/main.rs b/ls/src/main.rs index d095b742..4f5df1fc 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -24,7 +24,9 @@ fn main() { for file in files { match fs::read_dir(file) { Ok(dir) => { - let mut dir: Vec<_> = dir.map(|r| r.unwrap()).collect(); + let mut dir: Vec<_> = dir.map(|entry| { + File::from(entry.unwrap(), flags).unwrap() + }).collect(); if flags.time { if flags.last_accessed { @@ -64,18 +66,14 @@ fn main() { } /// Prints information about a file in the default format -fn print_default(dir: Vec, flags: LsFlags) -> i32 { +fn print_default(files: Vec, flags: LsFlags) -> i32 { let exit_code = 1; - for entry in dir { - if File::is_hidden(&entry) && !flags.all { + for file in files { + if File::is_hidden(&file.name) && !flags.all { continue; } - let path = entry.path(); - - let file = File::from(entry, fs::symlink_metadata(path).unwrap(), flags); - let file_name = file.get_file_name(); if flags.comma_separate { @@ -92,10 +90,8 @@ fn print_default(dir: Vec, flags: LsFlags) -> i32 { } /// Prints information about the provided file in a long format -fn print_list(dir: Vec, flags: LsFlags) -> i32 { - let mut exit_code = 1; - - let mut rows: Vec = Vec::new(); +fn print_list(files: Vec, flags: LsFlags) -> i32 { + let exit_code = 1; let mut block_width = 1; let mut hard_links_width = 1; @@ -103,84 +99,72 @@ fn print_list(dir: Vec, flags: LsFlags) -> i32 { let mut group_width = 1; let mut size_width = 1; - for entry in dir { - match fs::symlink_metadata(entry.path()) { - Ok(metadata) => { - if File::is_hidden(&entry) && !flags.all { - continue; - } - - let row = File::from(entry, metadata, flags); - - if flags.size { - let block = row.get_blocks().len(); + for file in &files { + if File::is_hidden(&file.name) && !flags.all { + continue; + } - if block > block_width { - block_width = block; - } - } + if flags.size { + let block = file.get_blocks().len(); - let hard_links = row.get_hard_links().len(); + if block > block_width { + block_width = block; + } + } - if hard_links > hard_links_width { - hard_links_width = hard_links; - } + let hard_links = file.get_hard_links().len(); - let user = row.get_user().len(); + if hard_links > hard_links_width { + hard_links_width = hard_links; + } - if user > user_width { - user_width = user; - } + let user = file.get_user().len(); - if !flags.no_owner { - let group = row.get_group().len(); + if user > user_width { + user_width = user; + } - if group > group_width { - group_width = group; - } - } + if !flags.no_owner { + let group = file.get_group().len(); - let size = row.get_size().len(); + if group > group_width { + group_width = group; + } + } - if size > size_width { - size_width = size; - } + let size = file.get_size().len(); - rows.push(row); - } - Err(err) => { - eprintln!("ls: {}", err); - exit_code = 1; - } + if size > size_width { + size_width = size; } } - for row in &rows { + for file in &files { if flags.size { print!( "{} ", - row.get_blocks().pad_to_width_with_alignment(block_width, Alignment::Right) + file.get_blocks().pad_to_width_with_alignment(block_width, Alignment::Right) ); } - print!("{} ", row.get_permissions()); + print!("{} ", file.get_permissions()); print!( "{} ", - row.get_hard_links().pad_to_width_with_alignment(hard_links_width, Alignment::Right) + file.get_hard_links().pad_to_width_with_alignment(hard_links_width, Alignment::Right) ); - print!("{} ", row.get_user().pad_to_width(user_width)); + print!("{} ", file.get_user().pad_to_width(user_width)); if !flags.no_owner { - print!("{} ", row.get_group().pad_to_width(group_width)); + print!("{} ", file.get_group().pad_to_width(group_width)); } - print!("{} ", row.get_size().pad_to_width_with_alignment(size_width, Alignment::Right)); + print!("{} ", file.get_size().pad_to_width_with_alignment(size_width, Alignment::Right)); - print!("{} ", row.get_time()); + print!("{} ", file.get_time()); - print!("{}", row.get_file_name()); + print!("{}", file.get_file_name()); println!(); } @@ -188,44 +172,24 @@ fn print_list(dir: Vec, flags: LsFlags) -> i32 { exit_code } -/// Sort a list of directories by last accessed time -fn sort_by_access_time(dir: &fs::DirEntry) -> SystemTime { - let metadata = dir.metadata(); +/// Sort a list of files by last accessed time +fn sort_by_access_time(file: &File) -> SystemTime { + let metadata = file.metadata.clone(); - if let Ok(metadata) = metadata { - metadata.accessed().unwrap() - } else { - SystemTime::now() - } + metadata.accessed().unwrap() } -/// Sort a list of directories by file name alphabetically -fn sort_by_name(dir: &fs::DirEntry) -> String { - let file_name = dir.file_name().into_string().unwrap(); - - file_name.to_lowercase() +/// Sort a list of files by file name alphabetically +fn sort_by_name(file: &File) -> String { + file.name.to_lowercase() } -/// Sort a list of directories by size -fn sort_by_size(dir: &fs::DirEntry) -> u64 { - let metadata = dir.metadata(); - - if let Ok(metadata) = metadata { - metadata.len() - } else { - let default_size: u64 = 0 as u64; - - default_size - } +/// Sort a list of files by size +fn sort_by_size(file: &File) -> u64 { + file.metadata.len() } /// Sort a list of directories by modification time -fn sort_by_time(dir: &fs::DirEntry) -> SystemTime { - let metadata = fs::metadata(dir.path()); - - if let Ok(metadata) = metadata { - metadata.modified().unwrap() - } else { - SystemTime::now() - } +fn sort_by_time(file: &File) -> SystemTime { + file.metadata.modified().unwrap() } From fc48f45950222ac1286b5a8be086aaf478b4830e Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Tue, 20 Oct 2020 12:01:51 -0500 Subject: [PATCH 044/133] Ls: switch to write --- ls/src/file.rs | 2 +- ls/src/main.rs | 52 +++++++++++++++++++++++++------------------------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/ls/src/file.rs b/ls/src/file.rs index 5964f6f8..4d264fef 100644 --- a/ls/src/file.rs +++ b/ls/src/file.rs @@ -114,7 +114,7 @@ impl File { result } - pub fn is_hidden(name: &String) -> bool { + pub fn is_hidden(name: &str) -> bool { name.starts_with('.') } diff --git a/ls/src/main.rs b/ls/src/main.rs index 4f5df1fc..9b683ccf 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -1,3 +1,4 @@ +use std::io::{self, Result, Write}; use std::string::String; use std::time::SystemTime; use std::{fs, process}; @@ -21,6 +22,8 @@ fn main() { let mut exit_code = 0; + let mut writer: Box = Box::new(io::stdout()); + for file in files { match fs::read_dir(file) { Ok(dir) => { @@ -48,9 +51,11 @@ fn main() { } if !flags.comma_separate && flags.show_list() { - exit_code = print_list(dir, flags); - } else { - exit_code = print_default(dir, flags); + if print_list(dir, &mut writer, flags).is_err() { + exit_code = 1 + } + } else if print_default(dir, &mut writer, flags).is_err() { + exit_code = 1; } } Err(err) => { @@ -66,9 +71,7 @@ fn main() { } /// Prints information about a file in the default format -fn print_default(files: Vec, flags: LsFlags) -> i32 { - let exit_code = 1; - +fn print_default(files: Vec, writer: &mut W, flags: LsFlags) -> Result<()> { for file in files { if File::is_hidden(&file.name) && !flags.all { continue; @@ -77,21 +80,20 @@ fn print_default(files: Vec, flags: LsFlags) -> i32 { let file_name = file.get_file_name(); if flags.comma_separate { - print!("{}, ", file_name); + write!(writer, "{}, ", file_name)?; } else { - println!("{}", file_name); + writeln!(writer, "{}", file_name)?; } } if flags.comma_separate { - println!(); + writeln!(writer)?; } - exit_code + Ok(()) } /// Prints information about the provided file in a long format -fn print_list(files: Vec, flags: LsFlags) -> i32 { - let exit_code = 1; +fn print_list(files: Vec, writer: &mut W, flags: LsFlags) -> Result<()> { let mut block_width = 1; let mut hard_links_width = 1; @@ -141,35 +143,33 @@ fn print_list(files: Vec, flags: LsFlags) -> i32 { for file in &files { if flags.size { - print!( - "{} ", - file.get_blocks().pad_to_width_with_alignment(block_width, Alignment::Right) - ); + write!(writer, "{}", file.get_blocks().pad_to_width_with_alignment(block_width, Alignment::Right))?; } - print!("{} ", file.get_permissions()); + write!(writer, "{} ", file.get_permissions())?; - print!( + write!( + writer, "{} ", file.get_hard_links().pad_to_width_with_alignment(hard_links_width, Alignment::Right) - ); + )?; - print!("{} ", file.get_user().pad_to_width(user_width)); + write!(writer, "{} ", file.get_user().pad_to_width(user_width))?; if !flags.no_owner { - print!("{} ", file.get_group().pad_to_width(group_width)); + write!(writer, "{} ", file.get_group().pad_to_width(group_width))?; } - print!("{} ", file.get_size().pad_to_width_with_alignment(size_width, Alignment::Right)); + write!(writer, "{} ", file.get_size().pad_to_width_with_alignment(size_width, Alignment::Right))?; - print!("{} ", file.get_time()); + write!(writer, "{} ", file.get_time())?; - print!("{}", file.get_file_name()); + write!(writer, "{}", file.get_file_name())?; - println!(); + writeln!(writer)?; } - exit_code + Ok(()) } /// Sort a list of files by last accessed time From bd2ed13b5f4667721cbca0c01668e29fcd807ea1 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Tue, 20 Oct 2020 12:07:15 -0500 Subject: [PATCH 045/133] Ls: remove name prefix --- ls/src/file.rs | 6 +++--- ls/src/flags.rs | 6 +++--- ls/src/main.rs | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/ls/src/file.rs b/ls/src/file.rs index 4d264fef..d1967f6b 100644 --- a/ls/src/file.rs +++ b/ls/src/file.rs @@ -11,17 +11,17 @@ extern crate chrono; use chrono::prelude::{DateTime, Local}; -use crate::flags::LsFlags; +use crate::flags::Flags; pub(crate) struct File { pub name: String, pub path: path::PathBuf, pub metadata: fs::Metadata, - flags: LsFlags, + flags: Flags, } impl File { - pub fn from(entry: fs::DirEntry, flags: LsFlags) -> io::Result { + pub fn from(entry: fs::DirEntry, flags: Flags) -> io::Result { let path = entry.path(); let metadata = fs::symlink_metadata(path.clone()).expect("Failed to read metadata"); diff --git a/ls/src/flags.rs b/ls/src/flags.rs index 3dba5809..b063fef9 100644 --- a/ls/src/flags.rs +++ b/ls/src/flags.rs @@ -1,7 +1,7 @@ use clap::ArgMatches; #[derive(Default, Copy, Clone)] -pub(crate) struct LsFlags { +pub(crate) struct Flags { pub all: bool, pub classify: bool, pub comma_separate: bool, @@ -16,7 +16,7 @@ pub(crate) struct LsFlags { pub time: bool, } -impl LsFlags { +impl Flags { pub fn from_matches(matches: &ArgMatches<'_>) -> Self { let all = matches.is_present("all"); let classify = matches.is_present("classify"); @@ -31,7 +31,7 @@ impl LsFlags { let sort_size = matches.is_present("sort_size"); let time = matches.is_present("time"); - LsFlags { + Flags { all, classify, comma_separate, diff --git a/ls/src/main.rs b/ls/src/main.rs index 9b683ccf..ed04d8aa 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -12,13 +12,13 @@ mod file; mod flags; use file::File; -use flags::LsFlags; +use flags::Flags; fn main() { let matches = cli::create_app().get_matches(); let files = matches.values_of("FILE").unwrap(); - let flags = LsFlags::from_matches(&matches); + let flags = Flags::from_matches(&matches); let mut exit_code = 0; @@ -71,7 +71,7 @@ fn main() { } /// Prints information about a file in the default format -fn print_default(files: Vec, writer: &mut W, flags: LsFlags) -> Result<()> { +fn print_default(files: Vec, writer: &mut W, flags: Flags) -> Result<()> { for file in files { if File::is_hidden(&file.name) && !flags.all { continue; @@ -93,7 +93,7 @@ fn print_default(files: Vec, writer: &mut W, flags: LsFlags) -> } /// Prints information about the provided file in a long format -fn print_list(files: Vec, writer: &mut W, flags: LsFlags) -> Result<()> { +fn print_list(files: Vec, writer: &mut W, flags: Flags) -> Result<()> { let mut block_width = 1; let mut hard_links_width = 1; From dcc5c8a76a14caa79f6eab156417f63e3f4ef4ea Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Tue, 20 Oct 2020 12:12:39 -0500 Subject: [PATCH 046/133] Ls: add -i flag --- ls/src/cli.rs | 6 ++++++ ls/src/file.rs | 4 ++++ ls/src/flags.rs | 3 +++ ls/src/main.rs | 13 +++++++++++++ 4 files changed, 26 insertions(+) diff --git a/ls/src/cli.rs b/ls/src/cli.rs index 666fba90..6794aab0 100644 --- a/ls/src/cli.rs +++ b/ls/src/cli.rs @@ -40,6 +40,12 @@ pub(crate) fn create_app<'a, 'b>() -> App<'a, 'b> { .help("Write a ( '/' ) after each filename if that file is a directory") .short("p"), ) + .arg( + Arg::with_name("inode") + .help("For each file, write the file's file serial number") + .short("i") + .long("inode"), + ) .arg( Arg::with_name("last_accessed") .help("Use time of last access instead of last modification of the file for sorting -t or writing -l") diff --git a/ls/src/file.rs b/ls/src/file.rs index d1967f6b..40b1b1fc 100644 --- a/ls/src/file.rs +++ b/ls/src/file.rs @@ -47,6 +47,10 @@ impl File { self.metadata.nlink().to_string() } + pub fn get_inode(&self) -> String { + self.metadata.ino().to_string() + } + /// Retrieves the file's user name as a string. If the `-n` flag is set, /// the the user's ID is returned pub fn get_user(&self) -> String { diff --git a/ls/src/flags.rs b/ls/src/flags.rs index b063fef9..fd9bbf09 100644 --- a/ls/src/flags.rs +++ b/ls/src/flags.rs @@ -6,6 +6,7 @@ pub(crate) struct Flags { pub classify: bool, pub comma_separate: bool, pub indicator: bool, + pub inode: bool, pub last_accessed: bool, pub list: bool, pub no_owner: bool, @@ -22,6 +23,7 @@ impl Flags { let classify = matches.is_present("classify"); let comma_separate = matches.is_present("comma_separate"); let indicator = matches.is_present("indicator"); + let inode = matches.is_present("inode"); let last_accessed = matches.is_present("last_accessed"); let list = matches.is_present("list"); let no_owner = matches.is_present("no_owner"); @@ -35,6 +37,7 @@ impl Flags { all, classify, comma_separate, + inode, indicator, last_accessed, list, diff --git a/ls/src/main.rs b/ls/src/main.rs index ed04d8aa..c9ef160b 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -95,6 +95,7 @@ fn print_default(files: Vec, writer: &mut W, flags: Flags) -> Re /// Prints information about the provided file in a long format fn print_list(files: Vec, writer: &mut W, flags: Flags) -> Result<()> { + let mut inode_width = 1; let mut block_width = 1; let mut hard_links_width = 1; let mut user_width = 1; @@ -106,6 +107,14 @@ fn print_list(files: Vec, writer: &mut W, flags: Flags) -> Resul continue; } + if flags.inode { + let inode = file.get_inode().len(); + + if inode > inode_width { + inode_width = inode; + } + } + if flags.size { let block = file.get_blocks().len(); @@ -142,6 +151,10 @@ fn print_list(files: Vec, writer: &mut W, flags: Flags) -> Resul } for file in &files { + if flags.inode { + write!(writer, "{} ", file.get_inode().pad_to_width_with_alignment(inode_width, Alignment::Right))?; + } + if flags.size { write!(writer, "{}", file.get_blocks().pad_to_width_with_alignment(block_width, Alignment::Right))?; } From df2b26e6bc4cb5c7babfb6f144207d3cf8a4c247 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Tue, 20 Oct 2020 12:14:46 -0500 Subject: [PATCH 047/133] Ls: cargo fmt --- ls/src/main.rs | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/ls/src/main.rs b/ls/src/main.rs index c9ef160b..f815e10e 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -27,9 +27,8 @@ fn main() { for file in files { match fs::read_dir(file) { Ok(dir) => { - let mut dir: Vec<_> = dir.map(|entry| { - File::from(entry.unwrap(), flags).unwrap() - }).collect(); + let mut dir: Vec<_> = + dir.map(|entry| File::from(entry.unwrap(), flags).unwrap()).collect(); if flags.time { if flags.last_accessed { @@ -94,7 +93,6 @@ fn print_default(files: Vec, writer: &mut W, flags: Flags) -> Re /// Prints information about the provided file in a long format fn print_list(files: Vec, writer: &mut W, flags: Flags) -> Result<()> { - let mut inode_width = 1; let mut block_width = 1; let mut hard_links_width = 1; @@ -152,11 +150,19 @@ fn print_list(files: Vec, writer: &mut W, flags: Flags) -> Resul for file in &files { if flags.inode { - write!(writer, "{} ", file.get_inode().pad_to_width_with_alignment(inode_width, Alignment::Right))?; + write!( + writer, + "{} ", + file.get_inode().pad_to_width_with_alignment(inode_width, Alignment::Right) + )?; } if flags.size { - write!(writer, "{}", file.get_blocks().pad_to_width_with_alignment(block_width, Alignment::Right))?; + write!( + writer, + "{}", + file.get_blocks().pad_to_width_with_alignment(block_width, Alignment::Right) + )?; } write!(writer, "{} ", file.get_permissions())?; @@ -173,7 +179,11 @@ fn print_list(files: Vec, writer: &mut W, flags: Flags) -> Resul write!(writer, "{} ", file.get_group().pad_to_width(group_width))?; } - write!(writer, "{} ", file.get_size().pad_to_width_with_alignment(size_width, Alignment::Right))?; + write!( + writer, + "{} ", + file.get_size().pad_to_width_with_alignment(size_width, Alignment::Right) + )?; write!(writer, "{} ", file.get_time())?; From 35749a9920bcec4944308111fd9d6b0ae75857bd Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Tue, 20 Oct 2020 14:31:24 -0500 Subject: [PATCH 048/133] Ls: implement -A flag --- ls/src/cli.rs | 8 +++++++- ls/src/flags.rs | 8 ++++++++ ls/src/main.rs | 10 +++++++--- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/ls/src/cli.rs b/ls/src/cli.rs index 6794aab0..f76eece0 100644 --- a/ls/src/cli.rs +++ b/ls/src/cli.rs @@ -20,10 +20,16 @@ pub(crate) fn create_app<'a, 'b>() -> App<'a, 'b> { ) .arg( Arg::with_name("all") - .help("Do not ignore entries starting with .") + .help("Write out all directory entries, including those whose names begin with a ( '.' )") .short("a") .long("all"), ) + .arg( + Arg::with_name("almost_all") + .help("Write out all directory entries, including those whose names begin with a ( '.' ) but excluding the entries dot and dot-dot (if they exist)") + .short("A") + .long("almost-all") + ) .arg( Arg::with_name("classify") .help("Append indicator (one of */=>@|) to entries") diff --git a/ls/src/flags.rs b/ls/src/flags.rs index fd9bbf09..411e5696 100644 --- a/ls/src/flags.rs +++ b/ls/src/flags.rs @@ -3,6 +3,7 @@ use clap::ArgMatches; #[derive(Default, Copy, Clone)] pub(crate) struct Flags { pub all: bool, + pub almost_all: bool, pub classify: bool, pub comma_separate: bool, pub indicator: bool, @@ -20,6 +21,7 @@ pub(crate) struct Flags { impl Flags { pub fn from_matches(matches: &ArgMatches<'_>) -> Self { let all = matches.is_present("all"); + let almost_all = matches.is_present("almost_all"); let classify = matches.is_present("classify"); let comma_separate = matches.is_present("comma_separate"); let indicator = matches.is_present("indicator"); @@ -35,6 +37,7 @@ impl Flags { Flags { all, + almost_all, classify, comma_separate, inode, @@ -54,4 +57,9 @@ impl Flags { pub fn show_list(&self) -> bool { !self.comma_separate && self.list || self.no_owner || self.numeric_uid_gid } + + /// Whether or not to show hidden files and directories + pub fn show_hidden(&self) -> bool { + self.all || self.almost_all + } } diff --git a/ls/src/main.rs b/ls/src/main.rs index f815e10e..f6630db6 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -27,6 +27,10 @@ fn main() { for file in files { match fs::read_dir(file) { Ok(dir) => { + if flags.all { + todo!(); + } + let mut dir: Vec<_> = dir.map(|entry| File::from(entry.unwrap(), flags).unwrap()).collect(); @@ -72,7 +76,7 @@ fn main() { /// Prints information about a file in the default format fn print_default(files: Vec, writer: &mut W, flags: Flags) -> Result<()> { for file in files { - if File::is_hidden(&file.name) && !flags.all { + if File::is_hidden(&file.name) && !flags.show_hidden() { continue; } @@ -101,7 +105,7 @@ fn print_list(files: Vec, writer: &mut W, flags: Flags) -> Resul let mut size_width = 1; for file in &files { - if File::is_hidden(&file.name) && !flags.all { + if File::is_hidden(&file.name) && !flags.show_hidden() { continue; } @@ -160,7 +164,7 @@ fn print_list(files: Vec, writer: &mut W, flags: Flags) -> Resul if flags.size { write!( writer, - "{}", + "{} ", file.get_blocks().pad_to_width_with_alignment(block_width, Alignment::Right) )?; } From 8f34012589fbb6bde4ec1e18e1415ffca48c90e0 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Tue, 20 Oct 2020 14:37:40 -0500 Subject: [PATCH 049/133] Ls: filter out hidden files --- ls/src/main.rs | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/ls/src/main.rs b/ls/src/main.rs index f6630db6..5bb61c4c 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -31,8 +31,18 @@ fn main() { todo!(); } - let mut dir: Vec<_> = - dir.map(|entry| File::from(entry.unwrap(), flags).unwrap()).collect(); + let mut dir: Vec<_> = dir + .map(|entry| File::from(entry.unwrap(), flags).unwrap()) + .filter(|file| { + let mut result = true; + + if File::is_hidden(&file.name) && !flags.show_hidden() { + result = false; + } + + result + }) + .collect(); if flags.time { if flags.last_accessed { @@ -76,10 +86,6 @@ fn main() { /// Prints information about a file in the default format fn print_default(files: Vec, writer: &mut W, flags: Flags) -> Result<()> { for file in files { - if File::is_hidden(&file.name) && !flags.show_hidden() { - continue; - } - let file_name = file.get_file_name(); if flags.comma_separate { @@ -95,7 +101,7 @@ fn print_default(files: Vec, writer: &mut W, flags: Flags) -> Re Ok(()) } -/// Prints information about the provided file in a long format +/// Prints information about the provided file in the long (`-l`) format fn print_list(files: Vec, writer: &mut W, flags: Flags) -> Result<()> { let mut inode_width = 1; let mut block_width = 1; @@ -105,10 +111,6 @@ fn print_list(files: Vec, writer: &mut W, flags: Flags) -> Resul let mut size_width = 1; for file in &files { - if File::is_hidden(&file.name) && !flags.show_hidden() { - continue; - } - if flags.inode { let inode = file.get_inode().len(); From 805aee312eb9c7670ac81a2f9aaa287a20079bc6 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Tue, 20 Oct 2020 14:53:09 -0500 Subject: [PATCH 050/133] Ls: simplify filter --- ls/src/main.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/ls/src/main.rs b/ls/src/main.rs index 5bb61c4c..d610a411 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -32,16 +32,11 @@ fn main() { } let mut dir: Vec<_> = dir + // Collect information about the file or directory .map(|entry| File::from(entry.unwrap(), flags).unwrap()) - .filter(|file| { - let mut result = true; - - if File::is_hidden(&file.name) && !flags.show_hidden() { - result = false; - } - - result - }) + // Hide hidden files and directories if `-a` or `-A` flags + // weren't provided + .filter(|file| !File::is_hidden(&file.name) || flags.show_hidden()) .collect(); if flags.time { From 235afdaa190878cfd0e24fc52b659e1cd609be49 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Tue, 20 Oct 2020 15:10:19 -0500 Subject: [PATCH 051/133] Ls: print file name when there are mutiple files --- ls/src/main.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/ls/src/main.rs b/ls/src/main.rs index d610a411..81861086 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -14,7 +14,7 @@ mod flags; use file::File; use flags::Flags; -fn main() { +fn main() -> io::Result<()> { let matches = cli::create_app().get_matches(); let files = matches.values_of("FILE").unwrap(); @@ -24,6 +24,8 @@ fn main() { let mut writer: Box = Box::new(io::stdout()); + let multiple = files.len() > 1; + for file in files { match fs::read_dir(file) { Ok(dir) => { @@ -34,7 +36,7 @@ fn main() { let mut dir: Vec<_> = dir // Collect information about the file or directory .map(|entry| File::from(entry.unwrap(), flags).unwrap()) - // Hide hidden files and directories if `-a` or `-A` flags + // Hide hidden files and directories if `-a` or `-A` flags // weren't provided .filter(|file| !File::is_hidden(&file.name) || flags.show_hidden()) .collect(); @@ -58,6 +60,10 @@ fn main() { dir.reverse(); } + if multiple { + writeln!(writer, "\n{}:", file)?; + } + if !flags.comma_separate && flags.show_list() { if print_list(dir, &mut writer, flags).is_err() { exit_code = 1 @@ -76,6 +82,8 @@ fn main() { if exit_code != 0 { process::exit(exit_code); } + + Ok(()) } /// Prints information about a file in the default format From 9397eec12ba26bf89d091d603a85d3d396d9b868 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Wed, 21 Oct 2020 10:44:04 -0500 Subject: [PATCH 052/133] Ls: add -L flag --- ls/src/cli.rs | 6 ++++++ ls/src/file.rs | 12 +++++++++++- ls/src/flags.rs | 3 +++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/ls/src/cli.rs b/ls/src/cli.rs index f76eece0..dbe2f3cb 100644 --- a/ls/src/cli.rs +++ b/ls/src/cli.rs @@ -41,6 +41,12 @@ pub(crate) fn create_app<'a, 'b>() -> App<'a, 'b> { .help("Fill width with a comma separated list of entries") .short("m"), ) + .arg( + Arg::with_name("dereference") + .help("When showing file information for a symbolic link, show information for the file the link references rather than for the link itself") + .short("L") + .long("dereference"), + ) .arg( Arg::with_name("indicator") .help("Write a ( '/' ) after each filename if that file is a directory") diff --git a/ls/src/file.rs b/ls/src/file.rs index 40b1b1fc..204e5807 100644 --- a/ls/src/file.rs +++ b/ls/src/file.rs @@ -25,6 +25,16 @@ impl File { let path = entry.path(); let metadata = fs::symlink_metadata(path.clone()).expect("Failed to read metadata"); + if flags.dereference && metadata.file_type().is_symlink() { + let symlink = fs::read_link(path.clone())?; + + let name: String = symlink.file_name().unwrap().to_str().unwrap().to_string(); + + let metadata = fs::metadata(&path).unwrap(); + + return Ok(File { name, path: symlink, metadata, flags }); + } + let name = entry.file_name().into_string().expect("Failed to get file name as string"); Ok(File { name, path, metadata, flags }) @@ -136,7 +146,7 @@ impl File { } } - if self.metadata.file_type().is_symlink() { + if self.metadata.file_type().is_symlink() && !flags.dereference { file_name = self.add_symlink_color(file_name); if flags.classify && !flags.show_list() { diff --git a/ls/src/flags.rs b/ls/src/flags.rs index 411e5696..8573f49f 100644 --- a/ls/src/flags.rs +++ b/ls/src/flags.rs @@ -6,6 +6,7 @@ pub(crate) struct Flags { pub almost_all: bool, pub classify: bool, pub comma_separate: bool, + pub dereference: bool, pub indicator: bool, pub inode: bool, pub last_accessed: bool, @@ -24,6 +25,7 @@ impl Flags { let almost_all = matches.is_present("almost_all"); let classify = matches.is_present("classify"); let comma_separate = matches.is_present("comma_separate"); + let dereference = matches.is_present("dereference"); let indicator = matches.is_present("indicator"); let inode = matches.is_present("inode"); let last_accessed = matches.is_present("last_accessed"); @@ -40,6 +42,7 @@ impl Flags { almost_all, classify, comma_separate, + dereference, inode, indicator, last_accessed, From f04275ea7bdca902953a2d5b9f0609708954f072 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Wed, 21 Oct 2020 11:33:57 -0500 Subject: [PATCH 053/133] Ls: add comments --- ls/src/file.rs | 1 + ls/src/flags.rs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/ls/src/file.rs b/ls/src/file.rs index 204e5807..42f68fac 100644 --- a/ls/src/file.rs +++ b/ls/src/file.rs @@ -13,6 +13,7 @@ use chrono::prelude::{DateTime, Local}; use crate::flags::Flags; +/// Represents a file and it's properties pub(crate) struct File { pub name: String, pub path: path::PathBuf, diff --git a/ls/src/flags.rs b/ls/src/flags.rs index 8573f49f..e097e894 100644 --- a/ls/src/flags.rs +++ b/ls/src/flags.rs @@ -1,5 +1,6 @@ use clap::ArgMatches; +/// Represents the command line arguments available to `ls` #[derive(Default, Copy, Clone)] pub(crate) struct Flags { pub all: bool, @@ -20,6 +21,7 @@ pub(crate) struct Flags { } impl Flags { + /// Create a `Flags` instance from the parsed command line arguments pub fn from_matches(matches: &ArgMatches<'_>) -> Self { let all = matches.is_present("all"); let almost_all = matches.is_present("almost_all"); From 1483a3b1d0d3cc09c073fbd5f260715cbde16585 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Wed, 21 Oct 2020 14:58:43 -0500 Subject: [PATCH 054/133] Ls: reduce code --- ls/src/main.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ls/src/main.rs b/ls/src/main.rs index 81861086..6f88ce8c 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -206,9 +206,7 @@ fn print_list(files: Vec, writer: &mut W, flags: Flags) -> Resul /// Sort a list of files by last accessed time fn sort_by_access_time(file: &File) -> SystemTime { - let metadata = file.metadata.clone(); - - metadata.accessed().unwrap() + file.metadata.accessed().unwrap() } /// Sort a list of files by file name alphabetically From c13e3b7ec8d02c8c13d735adbf272bbeed19e99b Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Wed, 21 Oct 2020 14:58:43 -0500 Subject: [PATCH 055/133] Ls: reduce code --- ls/src/main.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ls/src/main.rs b/ls/src/main.rs index 81861086..6f88ce8c 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -206,9 +206,7 @@ fn print_list(files: Vec, writer: &mut W, flags: Flags) -> Resul /// Sort a list of files by last accessed time fn sort_by_access_time(file: &File) -> SystemTime { - let metadata = file.metadata.clone(); - - metadata.accessed().unwrap() + file.metadata.accessed().unwrap() } /// Sort a list of files by file name alphabetically From ce748f403c1b1ae0ea14f73876cf432f5d70560d Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Wed, 21 Oct 2020 15:21:05 -0500 Subject: [PATCH 056/133] Ls: add comment --- ls/src/file.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/ls/src/file.rs b/ls/src/file.rs index 42f68fac..aae48006 100644 --- a/ls/src/file.rs +++ b/ls/src/file.rs @@ -22,6 +22,7 @@ pub(crate) struct File { } impl File { + /// Creates a `File` instance from a `DirEntry` pub fn from(entry: fs::DirEntry, flags: Flags) -> io::Result { let path = entry.path(); let metadata = fs::symlink_metadata(path.clone()).expect("Failed to read metadata"); From 75dfe23c9c4d12fce456cb4453456c6607a1ff10 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Thu, 22 Oct 2020 14:09:23 -0500 Subject: [PATCH 057/133] Ls: implement -a --- ls/src/file.rs | 27 +++++++++++++++++++++------ ls/src/main.rs | 23 ++++++++++++++++++----- 2 files changed, 39 insertions(+), 11 deletions(-) diff --git a/ls/src/file.rs b/ls/src/file.rs index aae48006..15d56a21 100644 --- a/ls/src/file.rs +++ b/ls/src/file.rs @@ -23,21 +23,30 @@ pub(crate) struct File { impl File { /// Creates a `File` instance from a `DirEntry` - pub fn from(entry: fs::DirEntry, flags: Flags) -> io::Result { - let path = entry.path(); - let metadata = fs::symlink_metadata(path.clone()).expect("Failed to read metadata"); + pub fn from(path: path::PathBuf, flags: Flags) -> io::Result { + let metadata = path.symlink_metadata().expect("Failed to read metadata?"); if flags.dereference && metadata.file_type().is_symlink() { let symlink = fs::read_link(path.clone())?; - let name: String = symlink.file_name().unwrap().to_str().unwrap().to_string(); + let name: String = File::path_to_file_name(&symlink); - let metadata = fs::metadata(&path).unwrap(); + let metadata = path.metadata().unwrap(); return Ok(File { name, path: symlink, metadata, flags }); } - let name = entry.file_name().into_string().expect("Failed to get file name as string"); + let name = File::path_to_file_name(&path); + + Ok(File { name, path, metadata, flags }) + } + + /// Creates a `File` instance from a `DirEntry` and supplies a file name + pub fn from_name(name: String, path: path::PathBuf, flags: Flags) -> io::Result { + if path.is_relative() { + println!("{}", File::path_to_file_name(&path)); + } + let metadata = path.metadata().expect("Failed to read metadata"); Ok(File { name, path, metadata, flags }) } @@ -134,6 +143,12 @@ impl File { name.starts_with('.') } + pub fn path_to_file_name(path: &path::PathBuf) -> String { + let file_name = path.file_name().expect("Failed to retrieve file name"); + + file_name.to_str().unwrap().to_string() + } + /// Gets a file name from a directory entry and adds appropriate formatting pub fn get_file_name(&self) -> String { let mut file_name = self.name.clone(); diff --git a/ls/src/main.rs b/ls/src/main.rs index 6f88ce8c..8b054b86 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -1,7 +1,7 @@ use std::io::{self, Result, Write}; use std::string::String; use std::time::SystemTime; -use std::{fs, process}; +use std::{fs, path, process}; use pad::{Alignment, PadStr}; @@ -29,13 +29,10 @@ fn main() -> io::Result<()> { for file in files { match fs::read_dir(file) { Ok(dir) => { - if flags.all { - todo!(); - } let mut dir: Vec<_> = dir // Collect information about the file or directory - .map(|entry| File::from(entry.unwrap(), flags).unwrap()) + .map(|entry| File::from(entry.unwrap().path(), flags).unwrap()) // Hide hidden files and directories if `-a` or `-A` flags // weren't provided .filter(|file| !File::is_hidden(&file.name) || flags.show_hidden()) @@ -60,6 +57,22 @@ fn main() -> io::Result<()> { dir.reverse(); } + if flags.all { + // Retrieve the current directories information. This must + // be canonicalize incase the path is relative + let current = path::PathBuf::from(file).canonicalize().unwrap(); + + let dot = File::from_name(".".to_string(), current.clone(), flags).expect("Failed to read ."); + + // Retrieve the parent path. Default to the current path if the parent doesn't exist + let parent_path = path::PathBuf::from(dot.path.parent().unwrap_or(current.as_path())); + + let dot_dot = File::from_name("..".to_string(), parent_path, flags).expect("Failed to read .."); + + dir.insert(0, dot); + dir.insert(1, dot_dot); + } + if multiple { writeln!(writer, "\n{}:", file)?; } From 7a8d6a0d882f96aaf153f143ebab141f09ec54c7 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Thu, 22 Oct 2020 15:54:43 -0500 Subject: [PATCH 058/133] Ls: clean up --- ls/src/file.rs | 3 --- ls/src/main.rs | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/ls/src/file.rs b/ls/src/file.rs index 15d56a21..b02af85b 100644 --- a/ls/src/file.rs +++ b/ls/src/file.rs @@ -43,9 +43,6 @@ impl File { /// Creates a `File` instance from a `DirEntry` and supplies a file name pub fn from_name(name: String, path: path::PathBuf, flags: Flags) -> io::Result { - if path.is_relative() { - println!("{}", File::path_to_file_name(&path)); - } let metadata = path.metadata().expect("Failed to read metadata"); Ok(File { name, path, metadata, flags }) diff --git a/ls/src/main.rs b/ls/src/main.rs index 8b054b86..9028da6d 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -65,7 +65,7 @@ fn main() -> io::Result<()> { let dot = File::from_name(".".to_string(), current.clone(), flags).expect("Failed to read ."); // Retrieve the parent path. Default to the current path if the parent doesn't exist - let parent_path = path::PathBuf::from(dot.path.parent().unwrap_or(current.as_path())); + let parent_path = path::PathBuf::from(dot.path.parent().unwrap_or_else(|| current.as_path())); let dot_dot = File::from_name("..".to_string(), parent_path, flags).expect("Failed to read .."); From 9983de855fcf5532dca847b3020a6adb5d036b72 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Thu, 22 Oct 2020 15:55:29 -0500 Subject: [PATCH 059/133] Ls: remove unnecessary line --- ls/src/main.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/ls/src/main.rs b/ls/src/main.rs index 9028da6d..036da28e 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -29,7 +29,6 @@ fn main() -> io::Result<()> { for file in files { match fs::read_dir(file) { Ok(dir) => { - let mut dir: Vec<_> = dir // Collect information about the file or directory .map(|entry| File::from(entry.unwrap().path(), flags).unwrap()) From 46728ba12d2d56d6afc9cd0e4baba73e4a9855de Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Fri, 23 Oct 2020 08:20:48 -0500 Subject: [PATCH 060/133] Ls: format with nightly --- ls/build.rs | 2 +- ls/src/cli.rs | 28 ++++++++++++++++++---------- ls/src/file.rs | 32 ++++++++++++-------------------- ls/src/flags.rs | 4 +--- ls/src/main.rs | 43 +++++++++++++++++++++---------------------- 5 files changed, 53 insertions(+), 56 deletions(-) diff --git a/ls/build.rs b/ls/build.rs index 019855cd..17f3863e 100644 --- a/ls/build.rs +++ b/ls/build.rs @@ -13,7 +13,7 @@ fn main() { Err(err) => { eprintln!("No OUT_DIR: {}", err); return; - } + }, }; app.gen_completions("ls", Shell::Zsh, out_dir.clone()); diff --git a/ls/src/cli.rs b/ls/src/cli.rs index dbe2f3cb..81a21e55 100644 --- a/ls/src/cli.rs +++ b/ls/src/cli.rs @@ -20,15 +20,21 @@ pub(crate) fn create_app<'a, 'b>() -> App<'a, 'b> { ) .arg( Arg::with_name("all") - .help("Write out all directory entries, including those whose names begin with a ( '.' )") + .help( + "Write out all directory entries, including those whose names begin with a \ + ( '.' )", + ) .short("a") .long("all"), ) .arg( Arg::with_name("almost_all") - .help("Write out all directory entries, including those whose names begin with a ( '.' ) but excluding the entries dot and dot-dot (if they exist)") + .help( + "Write out all directory entries, including those whose names begin with a \ + ( '.' ) but excluding the entries dot and dot-dot (if they exist)", + ) .short("A") - .long("almost-all") + .long("almost-all"), ) .arg( Arg::with_name("classify") @@ -43,7 +49,10 @@ pub(crate) fn create_app<'a, 'b>() -> App<'a, 'b> { ) .arg( Arg::with_name("dereference") - .help("When showing file information for a symbolic link, show information for the file the link references rather than for the link itself") + .help( + "When showing file information for a symbolic link, show information for the \ + file the link references rather than for the link itself", + ) .short("L") .long("dereference"), ) @@ -60,7 +69,10 @@ pub(crate) fn create_app<'a, 'b>() -> App<'a, 'b> { ) .arg( Arg::with_name("last_accessed") - .help("Use time of last access instead of last modification of the file for sorting -t or writing -l") + .help( + "Use time of last access instead of last modification of the file for sorting \ + -t or writing -l", + ) .short("u"), ) .arg(Arg::with_name("list").help("Use a long listing format").short("l")) @@ -83,10 +95,6 @@ pub(crate) fn create_app<'a, 'b>() -> App<'a, 'b> { .short("s") .long("size"), ) - .arg( - Arg::with_name("sort_size") - .help("Sort by first file size, largest first") - .short("S"), - ) + .arg(Arg::with_name("sort_size").help("Sort by first file size, largest first").short("S")) .arg(Arg::with_name("time").help("Sort by modification time, newest first").short("t")) } diff --git a/ls/src/file.rs b/ls/src/file.rs index b02af85b..b9586b96 100644 --- a/ls/src/file.rs +++ b/ls/src/file.rs @@ -1,9 +1,11 @@ -use coreutils_core::os::group::Group; -use coreutils_core::os::passwd::Passwd; +use coreutils_core::os::{group::Group, passwd::Passwd}; -use std::os::unix::fs::{FileTypeExt, MetadataExt, PermissionsExt}; -use std::string::String; -use std::{fs, io, path}; +use std::{ + fs, io, + os::unix::fs::{FileTypeExt, MetadataExt, PermissionsExt}, + path, + string::String, +}; use ansi_term::Color; @@ -49,9 +51,7 @@ impl File { } /// Retrieves the number of blocks allocated to a file as a string - pub fn get_blocks(&self) -> String { - self.metadata.blocks().to_string() - } + pub fn get_blocks(&self) -> String { self.metadata.blocks().to_string() } /// Retrieves a files permissions as a string pub fn get_permissions(&self) -> String { @@ -61,13 +61,9 @@ impl File { } /// Retrieves the number of hard links pointing to a file as a string - pub fn get_hard_links(&self) -> String { - self.metadata.nlink().to_string() - } + pub fn get_hard_links(&self) -> String { self.metadata.nlink().to_string() } - pub fn get_inode(&self) -> String { - self.metadata.ino().to_string() - } + pub fn get_inode(&self) -> String { self.metadata.ino().to_string() } /// Retrieves the file's user name as a string. If the `-n` flag is set, /// the the user's ID is returned @@ -104,9 +100,7 @@ impl File { } /// Retrieve the file's size, in bytes, as a string - pub fn get_size(&self) -> String { - self.metadata.len().to_string() - } + pub fn get_size(&self) -> String { self.metadata.len().to_string() } /// Retrieves the file's timestamp as a string pub fn get_time(&self) -> String { @@ -136,9 +130,7 @@ impl File { result } - pub fn is_hidden(name: &str) -> bool { - name.starts_with('.') - } + pub fn is_hidden(name: &str) -> bool { name.starts_with('.') } pub fn path_to_file_name(path: &path::PathBuf) -> String { let file_name = path.file_name().expect("Failed to retrieve file name"); diff --git a/ls/src/flags.rs b/ls/src/flags.rs index e097e894..b6aab7b0 100644 --- a/ls/src/flags.rs +++ b/ls/src/flags.rs @@ -64,7 +64,5 @@ impl Flags { } /// Whether or not to show hidden files and directories - pub fn show_hidden(&self) -> bool { - self.all || self.almost_all - } + pub fn show_hidden(&self) -> bool { self.all || self.almost_all } } diff --git a/ls/src/main.rs b/ls/src/main.rs index 036da28e..4a66a6eb 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -1,7 +1,10 @@ -use std::io::{self, Result, Write}; -use std::string::String; -use std::time::SystemTime; -use std::{fs, path, process}; +use std::{ + fs, + io::{self, Result, Write}, + path, process, + string::String, + time::SystemTime, +}; use pad::{Alignment, PadStr}; @@ -61,12 +64,16 @@ fn main() -> io::Result<()> { // be canonicalize incase the path is relative let current = path::PathBuf::from(file).canonicalize().unwrap(); - let dot = File::from_name(".".to_string(), current.clone(), flags).expect("Failed to read ."); + let dot = File::from_name(".".to_string(), current.clone(), flags) + .expect("Failed to read ."); - // Retrieve the parent path. Default to the current path if the parent doesn't exist - let parent_path = path::PathBuf::from(dot.path.parent().unwrap_or_else(|| current.as_path())); + // Retrieve the parent path. Default to the current path if the parent doesn't + // exist + let parent_path = + path::PathBuf::from(dot.path.parent().unwrap_or_else(|| current.as_path())); - let dot_dot = File::from_name("..".to_string(), parent_path, flags).expect("Failed to read .."); + let dot_dot = File::from_name("..".to_string(), parent_path, flags) + .expect("Failed to read .."); dir.insert(0, dot); dir.insert(1, dot_dot); @@ -83,11 +90,11 @@ fn main() -> io::Result<()> { } else if print_default(dir, &mut writer, flags).is_err() { exit_code = 1; } - } + }, Err(err) => { eprintln!("ls: cannot access '{}': {}", file, err); exit_code = 1; - } + }, } } @@ -217,21 +224,13 @@ fn print_list(files: Vec, writer: &mut W, flags: Flags) -> Resul } /// Sort a list of files by last accessed time -fn sort_by_access_time(file: &File) -> SystemTime { - file.metadata.accessed().unwrap() -} +fn sort_by_access_time(file: &File) -> SystemTime { file.metadata.accessed().unwrap() } /// Sort a list of files by file name alphabetically -fn sort_by_name(file: &File) -> String { - file.name.to_lowercase() -} +fn sort_by_name(file: &File) -> String { file.name.to_lowercase() } /// Sort a list of files by size -fn sort_by_size(file: &File) -> u64 { - file.metadata.len() -} +fn sort_by_size(file: &File) -> u64 { file.metadata.len() } /// Sort a list of directories by modification time -fn sort_by_time(file: &File) -> SystemTime { - file.metadata.modified().unwrap() -} +fn sort_by_time(file: &File) -> SystemTime { file.metadata.modified().unwrap() } From 71162af08334a3df7173c583c4c48a0687966323 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Fri, 23 Oct 2020 11:47:04 -0500 Subject: [PATCH 061/133] Ls: add long options --- ls/src/cli.rs | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/ls/src/cli.rs b/ls/src/cli.rs index 81a21e55..fa341ff1 100644 --- a/ls/src/cli.rs +++ b/ls/src/cli.rs @@ -45,7 +45,8 @@ pub(crate) fn create_app<'a, 'b>() -> App<'a, 'b> { .arg( Arg::with_name("comma_separate") .help("Fill width with a comma separated list of entries") - .short("m"), + .short("m") + .long("comma_separate"), ) .arg( Arg::with_name("dereference") @@ -59,7 +60,8 @@ pub(crate) fn create_app<'a, 'b>() -> App<'a, 'b> { .arg( Arg::with_name("indicator") .help("Write a ( '/' ) after each filename if that file is a directory") - .short("p"), + .short("p") + .long("indicator"), ) .arg( Arg::with_name("inode") @@ -73,10 +75,16 @@ pub(crate) fn create_app<'a, 'b>() -> App<'a, 'b> { "Use time of last access instead of last modification of the file for sorting \ -t or writing -l", ) - .short("u"), + .short("u") + .long("last-accessed"), + ) + .arg(Arg::with_name("list").help("Use a long listing format").short("l").long("list")) + .arg( + Arg::with_name("no_owner") + .help("Like -l, but do not list owner") + .short("g") + .long("no-owner"), ) - .arg(Arg::with_name("list").help("Use a long listing format").short("l")) - .arg(Arg::with_name("no_owner").help("Like -l, but do not list owner").short("g")) .arg( Arg::with_name("numeric_uid_gid") .help("Like -l, but list numeric user and group IDs") @@ -95,6 +103,16 @@ pub(crate) fn create_app<'a, 'b>() -> App<'a, 'b> { .short("s") .long("size"), ) - .arg(Arg::with_name("sort_size").help("Sort by first file size, largest first").short("S")) - .arg(Arg::with_name("time").help("Sort by modification time, newest first").short("t")) + .arg( + Arg::with_name("sort_size") + .help("Sort by first file size, largest first") + .short("S") + .long("sort-size"), + ) + .arg( + Arg::with_name("time") + .help("Sort by modification time, newest first") + .short("t") + .long("time"), + ) } From 98d97753dfcb522116c35d526cf157de471ae775 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Fri, 23 Oct 2020 15:40:23 -0500 Subject: [PATCH 062/133] Ls: implement -f flag --- ls/src/cli.rs | 9 +++++++++ ls/src/flags.rs | 5 ++++- ls/src/main.rs | 32 +++++++++++++++++--------------- 3 files changed, 30 insertions(+), 16 deletions(-) diff --git a/ls/src/cli.rs b/ls/src/cli.rs index fa341ff1..ac6f7f49 100644 --- a/ls/src/cli.rs +++ b/ls/src/cli.rs @@ -36,6 +36,15 @@ pub(crate) fn create_app<'a, 'b>() -> App<'a, 'b> { .short("A") .long("almost-all"), ) + .arg( + Arg::with_name("no_sort") + .help( + "Output is not sorted. This option turns on -a. It also negates the effect of \ + the -r, -S and -t options.", + ) + .short("f") + .long("no-sort"), + ) .arg( Arg::with_name("classify") .help("Append indicator (one of */=>@|) to entries") diff --git a/ls/src/flags.rs b/ls/src/flags.rs index b6aab7b0..79756d5a 100644 --- a/ls/src/flags.rs +++ b/ls/src/flags.rs @@ -13,6 +13,7 @@ pub(crate) struct Flags { pub last_accessed: bool, pub list: bool, pub no_owner: bool, + pub no_sort: bool, pub numeric_uid_gid: bool, pub reverse: bool, pub size: bool, @@ -33,6 +34,7 @@ impl Flags { let last_accessed = matches.is_present("last_accessed"); let list = matches.is_present("list"); let no_owner = matches.is_present("no_owner"); + let no_sort = matches.is_present("no_sort"); let numeric_uid_gid = matches.is_present("numeric_uid_gid"); let reverse = matches.is_present("reverse"); let size = matches.is_present("size"); @@ -50,6 +52,7 @@ impl Flags { last_accessed, list, no_owner, + no_sort, numeric_uid_gid, reverse, size, @@ -64,5 +67,5 @@ impl Flags { } /// Whether or not to show hidden files and directories - pub fn show_hidden(&self) -> bool { self.all || self.almost_all } + pub fn show_hidden(&self) -> bool { self.all || self.almost_all || self.no_sort } } diff --git a/ls/src/main.rs b/ls/src/main.rs index 4a66a6eb..46602581 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -40,26 +40,28 @@ fn main() -> io::Result<()> { .filter(|file| !File::is_hidden(&file.name) || flags.show_hidden()) .collect(); - if flags.time { - if flags.last_accessed { - dir.sort_by_key(sort_by_access_time); + if !flags.no_sort { + if flags.time { + if flags.last_accessed { + dir.sort_by_key(sort_by_access_time); + } else { + dir.sort_by_key(sort_by_time); + } + dir.reverse(); + } else if flags.sort_size { + dir.sort_by_key(sort_by_size); + dir.reverse(); } else { - dir.sort_by_key(sort_by_time); + // Sort the directory entries by file name by default + dir.sort_by_key(sort_by_name); } - dir.reverse(); - } else if flags.sort_size { - dir.sort_by_key(sort_by_size); - dir.reverse(); - } else { - // Sort the directory entries by file name by default - dir.sort_by_key(sort_by_name); - } - if flags.reverse { - dir.reverse(); + if flags.reverse { + dir.reverse(); + } } - if flags.all { + if flags.all || flags.no_sort { // Retrieve the current directories information. This must // be canonicalize incase the path is relative let current = path::PathBuf::from(file).canonicalize().unwrap(); From 9532c4b07896840969768e3ddb0392ed08ce84dc Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Mon, 26 Oct 2020 11:46:16 -0500 Subject: [PATCH 063/133] Ls: use different time format depending on duration --- ls/src/file.rs | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/ls/src/file.rs b/ls/src/file.rs index b9586b96..5ed8ad96 100644 --- a/ls/src/file.rs +++ b/ls/src/file.rs @@ -11,7 +11,7 @@ use ansi_term::Color; extern crate chrono; -use chrono::prelude::{DateTime, Local}; +use chrono::{DateTime, Local}; use crate::flags::Flags; @@ -103,6 +103,12 @@ impl File { pub fn get_size(&self) -> String { self.metadata.len().to_string() } /// Retrieves the file's timestamp as a string + /// + /// By default the file's modified time is displayed. The `-u` flag will + /// display the last accessed time. The `-c` flag will display the last + /// modified time of the file's status information. The date format used is + /// `%b %e %H:%M` unless the duration is greater than six months, which case + /// the date format will be `%b %e %Y`. pub fn get_time(&self) -> String { let datetime: DateTime; @@ -114,7 +120,19 @@ impl File { datetime = modified.into(); } - datetime.format("%b %e %k:%M").to_string() + let mut fmt = "%b %e %H:%M"; + + let now: DateTime = Local::now(); + + let duration = datetime.signed_duration_since(now); + + let six_months = -182; + + if duration.num_days() < six_months { + fmt = "%b %e %Y"; + } + + datetime.format(fmt).to_string() } /// Check if a path is an executable file From 123017c9d5efee99f2b6c3e2bc0eaff73f70dc50 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Tue, 27 Oct 2020 11:20:48 -0500 Subject: [PATCH 064/133] Ls: add utility to all platforms --- DragonflyBSD.toml | 1 + FreeBSD.toml | 1 + Fuchsia.toml | 1 + Haiku.toml | 1 + Linux.toml | 1 + MacOS.toml | 1 + NetBSD.toml | 1 + OpenBSD.toml | 1 + Solaris.toml | 1 + Unix.toml | 1 + 10 files changed, 10 insertions(+) diff --git a/DragonflyBSD.toml b/DragonflyBSD.toml index a0e9d03f..e581981a 100644 --- a/DragonflyBSD.toml +++ b/DragonflyBSD.toml @@ -23,6 +23,7 @@ members = [ "groups", "head", "id", + 'ls', "link", "logname", "mkdir", diff --git a/FreeBSD.toml b/FreeBSD.toml index a0e9d03f..e581981a 100644 --- a/FreeBSD.toml +++ b/FreeBSD.toml @@ -23,6 +23,7 @@ members = [ "groups", "head", "id", + 'ls', "link", "logname", "mkdir", diff --git a/Fuchsia.toml b/Fuchsia.toml index 030578a0..b51f4965 100644 --- a/Fuchsia.toml +++ b/Fuchsia.toml @@ -23,6 +23,7 @@ members = [ "groups", "head", "id", + 'ls', "link", "logname", "mkdir", diff --git a/Haiku.toml b/Haiku.toml index 030578a0..b51f4965 100644 --- a/Haiku.toml +++ b/Haiku.toml @@ -23,6 +23,7 @@ members = [ "groups", "head", "id", + 'ls', "link", "logname", "mkdir", diff --git a/Linux.toml b/Linux.toml index a0e9d03f..c71c53b6 100644 --- a/Linux.toml +++ b/Linux.toml @@ -24,6 +24,7 @@ members = [ "head", "id", "link", + 'ls', "logname", "mkdir", "mkfifo", diff --git a/MacOS.toml b/MacOS.toml index a0e9d03f..e581981a 100644 --- a/MacOS.toml +++ b/MacOS.toml @@ -23,6 +23,7 @@ members = [ "groups", "head", "id", + 'ls', "link", "logname", "mkdir", diff --git a/NetBSD.toml b/NetBSD.toml index a0e9d03f..e581981a 100644 --- a/NetBSD.toml +++ b/NetBSD.toml @@ -23,6 +23,7 @@ members = [ "groups", "head", "id", + 'ls', "link", "logname", "mkdir", diff --git a/OpenBSD.toml b/OpenBSD.toml index a0e9d03f..e581981a 100644 --- a/OpenBSD.toml +++ b/OpenBSD.toml @@ -23,6 +23,7 @@ members = [ "groups", "head", "id", + 'ls', "link", "logname", "mkdir", diff --git a/Solaris.toml b/Solaris.toml index a0e9d03f..e581981a 100644 --- a/Solaris.toml +++ b/Solaris.toml @@ -23,6 +23,7 @@ members = [ "groups", "head", "id", + 'ls', "link", "logname", "mkdir", diff --git a/Unix.toml b/Unix.toml index a0e9d03f..e581981a 100644 --- a/Unix.toml +++ b/Unix.toml @@ -23,6 +23,7 @@ members = [ "groups", "head", "id", + 'ls', "link", "logname", "mkdir", From e12e37e7a509ef868ecb846f3fe06644aa7eef99 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Tue, 27 Oct 2020 11:21:35 -0500 Subject: [PATCH 065/133] Ls: append period to help text --- ls/src/cli.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ls/src/cli.rs b/ls/src/cli.rs index ac6f7f49..c5297e50 100644 --- a/ls/src/cli.rs +++ b/ls/src/cli.rs @@ -22,7 +22,7 @@ pub(crate) fn create_app<'a, 'b>() -> App<'a, 'b> { Arg::with_name("all") .help( "Write out all directory entries, including those whose names begin with a \ - ( '.' )", + ( '.' ).", ) .short("a") .long("all"), From c2d94d953d02acd0db4ad33035885eccfabb4d69 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Tue, 27 Oct 2020 11:23:30 -0500 Subject: [PATCH 066/133] Ls: remove panics --- ls/src/file.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ls/src/file.rs b/ls/src/file.rs index 5ed8ad96..d690555b 100644 --- a/ls/src/file.rs +++ b/ls/src/file.rs @@ -26,14 +26,14 @@ pub(crate) struct File { impl File { /// Creates a `File` instance from a `DirEntry` pub fn from(path: path::PathBuf, flags: Flags) -> io::Result { - let metadata = path.symlink_metadata().expect("Failed to read metadata?"); + let metadata = path.symlink_metadata()?; if flags.dereference && metadata.file_type().is_symlink() { let symlink = fs::read_link(path.clone())?; let name: String = File::path_to_file_name(&symlink); - let metadata = path.metadata().unwrap(); + let metadata = path.metadata()?; return Ok(File { name, path: symlink, metadata, flags }); } @@ -45,7 +45,7 @@ impl File { /// Creates a `File` instance from a `DirEntry` and supplies a file name pub fn from_name(name: String, path: path::PathBuf, flags: Flags) -> io::Result { - let metadata = path.metadata().expect("Failed to read metadata"); + let metadata = path.metadata()?; Ok(File { name, path, metadata, flags }) } From 03915d16c6771c9047c25d3872ff73fa2645e4d3 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Tue, 27 Oct 2020 11:41:39 -0500 Subject: [PATCH 067/133] Ls: use proper naming for getters --- ls/src/file.rs | 18 +++++++++--------- ls/src/main.rs | 32 ++++++++++++++++---------------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/ls/src/file.rs b/ls/src/file.rs index d690555b..f73ea5b6 100644 --- a/ls/src/file.rs +++ b/ls/src/file.rs @@ -51,23 +51,23 @@ impl File { } /// Retrieves the number of blocks allocated to a file as a string - pub fn get_blocks(&self) -> String { self.metadata.blocks().to_string() } + pub fn blocks(&self) -> String { self.metadata.blocks().to_string() } /// Retrieves a files permissions as a string - pub fn get_permissions(&self) -> String { + pub fn permissions(&self) -> String { let mode = self.metadata.permissions().mode(); unix_mode::to_string(mode) } /// Retrieves the number of hard links pointing to a file as a string - pub fn get_hard_links(&self) -> String { self.metadata.nlink().to_string() } + pub fn hard_links(&self) -> String { self.metadata.nlink().to_string() } - pub fn get_inode(&self) -> String { self.metadata.ino().to_string() } + pub fn inode(&self) -> String { self.metadata.ino().to_string() } /// Retrieves the file's user name as a string. If the `-n` flag is set, /// the the user's ID is returned - pub fn get_user(&self) -> String { + pub fn user(&self) -> String { let user: String; if self.flags.numeric_uid_gid { @@ -84,7 +84,7 @@ impl File { /// Retrieves the file's group name as a string. If the `-n` flag is set, /// the the group's ID is returned - pub fn get_group(&self) -> String { + pub fn group(&self) -> String { let group: String; if self.flags.numeric_uid_gid { @@ -100,7 +100,7 @@ impl File { } /// Retrieve the file's size, in bytes, as a string - pub fn get_size(&self) -> String { self.metadata.len().to_string() } + pub fn size(&self) -> String { self.metadata.len().to_string() } /// Retrieves the file's timestamp as a string /// @@ -109,7 +109,7 @@ impl File { /// modified time of the file's status information. The date format used is /// `%b %e %H:%M` unless the duration is greater than six months, which case /// the date format will be `%b %e %Y`. - pub fn get_time(&self) -> String { + pub fn time(&self) -> String { let datetime: DateTime; if self.flags.last_accessed { @@ -157,7 +157,7 @@ impl File { } /// Gets a file name from a directory entry and adds appropriate formatting - pub fn get_file_name(&self) -> String { + pub fn file_name(&self) -> String { let mut file_name = self.name.clone(); let flags = self.flags; diff --git a/ls/src/main.rs b/ls/src/main.rs index 46602581..0fe0f29e 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -110,7 +110,7 @@ fn main() -> io::Result<()> { /// Prints information about a file in the default format fn print_default(files: Vec, writer: &mut W, flags: Flags) -> Result<()> { for file in files { - let file_name = file.get_file_name(); + let file_name = file.file_name(); if flags.comma_separate { write!(writer, "{}, ", file_name)?; @@ -136,7 +136,7 @@ fn print_list(files: Vec, writer: &mut W, flags: Flags) -> Resul for file in &files { if flags.inode { - let inode = file.get_inode().len(); + let inode = file.inode().len(); if inode > inode_width { inode_width = inode; @@ -144,34 +144,34 @@ fn print_list(files: Vec, writer: &mut W, flags: Flags) -> Resul } if flags.size { - let block = file.get_blocks().len(); + let block = file.blocks().len(); if block > block_width { block_width = block; } } - let hard_links = file.get_hard_links().len(); + let hard_links = file.hard_links().len(); if hard_links > hard_links_width { hard_links_width = hard_links; } - let user = file.get_user().len(); + let user = file.user().len(); if user > user_width { user_width = user; } if !flags.no_owner { - let group = file.get_group().len(); + let group = file.group().len(); if group > group_width { group_width = group; } } - let size = file.get_size().len(); + let size = file.size().len(); if size > size_width { size_width = size; @@ -183,7 +183,7 @@ fn print_list(files: Vec, writer: &mut W, flags: Flags) -> Resul write!( writer, "{} ", - file.get_inode().pad_to_width_with_alignment(inode_width, Alignment::Right) + file.inode().pad_to_width_with_alignment(inode_width, Alignment::Right) )?; } @@ -191,33 +191,33 @@ fn print_list(files: Vec, writer: &mut W, flags: Flags) -> Resul write!( writer, "{} ", - file.get_blocks().pad_to_width_with_alignment(block_width, Alignment::Right) + file.blocks().pad_to_width_with_alignment(block_width, Alignment::Right) )?; } - write!(writer, "{} ", file.get_permissions())?; + write!(writer, "{} ", file.permissions())?; write!( writer, "{} ", - file.get_hard_links().pad_to_width_with_alignment(hard_links_width, Alignment::Right) + file.hard_links().pad_to_width_with_alignment(hard_links_width, Alignment::Right) )?; - write!(writer, "{} ", file.get_user().pad_to_width(user_width))?; + write!(writer, "{} ", file.user().pad_to_width(user_width))?; if !flags.no_owner { - write!(writer, "{} ", file.get_group().pad_to_width(group_width))?; + write!(writer, "{} ", file.group().pad_to_width(group_width))?; } write!( writer, "{} ", - file.get_size().pad_to_width_with_alignment(size_width, Alignment::Right) + file.size().pad_to_width_with_alignment(size_width, Alignment::Right) )?; - write!(writer, "{} ", file.get_time())?; + write!(writer, "{} ", file.time())?; - write!(writer, "{}", file.get_file_name())?; + write!(writer, "{}", file.file_name())?; writeln!(writer)?; } From 45b83f1a744d29ffb3e25b0c110e0855f9ea91ff Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Tue, 27 Oct 2020 15:39:19 -0500 Subject: [PATCH 068/133] Ls: return result rather than panic --- ls/src/file.rs | 38 +++++++++++++++++--------------------- ls/src/main.rs | 50 +++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 60 insertions(+), 28 deletions(-) diff --git a/ls/src/file.rs b/ls/src/file.rs index f73ea5b6..c46d04fa 100644 --- a/ls/src/file.rs +++ b/ls/src/file.rs @@ -1,9 +1,13 @@ -use coreutils_core::os::{group::Group, passwd::Passwd}; +use coreutils_core::os::{ + group::{Error as GroupError, Group}, + passwd::{Error as PasswdError, Passwd}, +}; use std::{ fs, io, os::unix::fs::{FileTypeExt, MetadataExt, PermissionsExt}, path, + result::Result, string::String, }; @@ -67,36 +71,28 @@ impl File { /// Retrieves the file's user name as a string. If the `-n` flag is set, /// the the user's ID is returned - pub fn user(&self) -> String { - let user: String; - + pub fn user(&self) -> Result { if self.flags.numeric_uid_gid { - let user_value = self.metadata.uid(); - user = user_value.to_string(); - } else { - let uid = Passwd::from_uid(self.metadata.uid()).unwrap(); - let user_value = uid.name(); - user = user_value.to_string(); + return Ok(self.metadata.uid().to_string()); } - user + match Passwd::from_uid(self.metadata.uid()) { + Ok(passwd) => Ok(passwd.name().to_string()), + Err(err) => Err(err), + } } /// Retrieves the file's group name as a string. If the `-n` flag is set, /// the the group's ID is returned - pub fn group(&self) -> String { - let group: String; - + pub fn group(&self) -> Result { if self.flags.numeric_uid_gid { - let group_value = self.metadata.gid(); - group = group_value.to_string(); - } else { - let gid = Group::from_gid(self.metadata.gid()).unwrap(); - let group_value = gid.name(); - group = group_value.to_string(); + return Ok(self.metadata.gid().to_string()); } - group + match Group::from_gid(self.metadata.gid()) { + Ok(group) => Ok(group.name().to_string()), + Err(err) => Err(err), + } } /// Retrieve the file's size, in bytes, as a string diff --git a/ls/src/main.rs b/ls/src/main.rs index 0fe0f29e..948b05da 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -1,6 +1,6 @@ use std::{ fs, - io::{self, Result, Write}, + io::{self, Write}, path, process, string::String, time::SystemTime, @@ -108,7 +108,7 @@ fn main() -> io::Result<()> { } /// Prints information about a file in the default format -fn print_default(files: Vec, writer: &mut W, flags: Flags) -> Result<()> { +fn print_default(files: Vec, writer: &mut W, flags: Flags) -> io::Result<()> { for file in files { let file_name = file.file_name(); @@ -126,7 +126,7 @@ fn print_default(files: Vec, writer: &mut W, flags: Flags) -> Re } /// Prints information about the provided file in the long (`-l`) format -fn print_list(files: Vec, writer: &mut W, flags: Flags) -> Result<()> { +fn print_list(files: Vec, writer: &mut W, flags: Flags) -> io::Result<()> { let mut inode_width = 1; let mut block_width = 1; let mut hard_links_width = 1; @@ -157,14 +157,34 @@ fn print_list(files: Vec, writer: &mut W, flags: Flags) -> Resul hard_links_width = hard_links; } - let user = file.user().len(); + let user: usize; + + match file.user() { + Ok(file_user) => { + user = file_user.len(); + }, + Err(err) => { + eprintln!("ls: {}", err); + process::exit(1); + }, + } if user > user_width { user_width = user; } if !flags.no_owner { - let group = file.group().len(); + let group: usize; + + match file.group() { + Ok(file_group) => { + group = file_group.len(); + }, + Err(err) => { + eprintln!("ls: {}", err); + process::exit(1); + }, + } if group > group_width { group_width = group; @@ -203,10 +223,26 @@ fn print_list(files: Vec, writer: &mut W, flags: Flags) -> Resul file.hard_links().pad_to_width_with_alignment(hard_links_width, Alignment::Right) )?; - write!(writer, "{} ", file.user().pad_to_width(user_width))?; + match file.user() { + Ok(user) => { + write!(writer, "{} ", user.pad_to_width(user_width))?; + }, + Err(err) => { + eprintln!("ls: {}", err); + process::exit(1); + }, + } if !flags.no_owner { - write!(writer, "{} ", file.group().pad_to_width(group_width))?; + match file.group() { + Ok(group) => { + write!(writer, "{} ", group.pad_to_width(group_width))?; + }, + Err(err) => { + eprintln!("ls: {}", err); + process::exit(1); + }, + } } write!( From e4aaa30fdcb12b9f661def06862d8ef70ba841ec Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Wed, 28 Oct 2020 09:49:55 -0500 Subject: [PATCH 069/133] Ls: return result rather than panic --- ls/src/file.rs | 18 ++++++++---------- ls/src/main.rs | 2 +- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/ls/src/file.rs b/ls/src/file.rs index c46d04fa..f954b8c2 100644 --- a/ls/src/file.rs +++ b/ls/src/file.rs @@ -105,16 +105,14 @@ impl File { /// modified time of the file's status information. The date format used is /// `%b %e %H:%M` unless the duration is greater than six months, which case /// the date format will be `%b %e %Y`. - pub fn time(&self) -> String { - let datetime: DateTime; - - if self.flags.last_accessed { - let accessed = self.metadata.accessed().unwrap(); - datetime = accessed.into(); + pub fn time(&self) -> io::Result { + let system_time = if self.flags.last_accessed { + self.metadata.accessed()? } else { - let modified = self.metadata.modified().unwrap(); - datetime = modified.into(); - } + self.metadata.modified()? + }; + + let datetime: DateTime = system_time.into(); let mut fmt = "%b %e %H:%M"; @@ -128,7 +126,7 @@ impl File { fmt = "%b %e %Y"; } - datetime.format(fmt).to_string() + Ok(datetime.format(fmt).to_string()) } /// Check if a path is an executable file diff --git a/ls/src/main.rs b/ls/src/main.rs index 948b05da..a9b530b0 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -251,7 +251,7 @@ fn print_list(files: Vec, writer: &mut W, flags: Flags) -> io::R file.size().pad_to_width_with_alignment(size_width, Alignment::Right) )?; - write!(writer, "{} ", file.time())?; + write!(writer, "{} ", file.time()?)?; write!(writer, "{}", file.file_name())?; From bc47f51c88e4fbffd167dcdc017fc4e9118eafd5 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Thu, 29 Oct 2020 11:04:04 -0500 Subject: [PATCH 070/133] Ls: return result rather than panic --- ls/src/file.rs | 32 +++++++++++++++++++++++++++----- ls/src/main.rs | 18 ++++++++++++++---- 2 files changed, 41 insertions(+), 9 deletions(-) diff --git a/ls/src/file.rs b/ls/src/file.rs index f954b8c2..b4ba5ce1 100644 --- a/ls/src/file.rs +++ b/ls/src/file.rs @@ -35,14 +35,14 @@ impl File { if flags.dereference && metadata.file_type().is_symlink() { let symlink = fs::read_link(path.clone())?; - let name: String = File::path_to_file_name(&symlink); + let name = File::path_buf_to_file_name(&symlink)?.to_string(); let metadata = path.metadata()?; return Ok(File { name, path: symlink, metadata, flags }); } - let name = File::path_to_file_name(&path); + let name = File::path_buf_to_file_name(&path)?.to_string(); Ok(File { name, path, metadata, flags }) } @@ -144,10 +144,32 @@ impl File { pub fn is_hidden(name: &str) -> bool { name.starts_with('.') } - pub fn path_to_file_name(path: &path::PathBuf) -> String { - let file_name = path.file_name().expect("Failed to retrieve file name"); + /// Gets the file name from a `PathBuf` + /// + /// Will return `Error` if the path terminates at '..' or if the file name + /// contains invalid unicode characters. + pub fn path_buf_to_file_name(path: &path::PathBuf) -> io::Result<&str> { + // Create a new IO Error. + let io_error = |kind: io::ErrorKind, msg: &str| io::Error::new(kind, msg); + + let file_name = match path.file_name() { + Some(file_name) => file_name, + None => { + return Err(io_error(io::ErrorKind::NotFound, "Path terminates at \"..\"")); + }, + }; + + let file_name = match file_name.to_str() { + Some(file_name) => file_name, + None => { + return Err(io_error( + io::ErrorKind::InvalidData, + "File name contains invalid unicode", + )); + }, + }; - file_name.to_str().unwrap().to_string() + Ok(file_name) } /// Gets a file name from a directory entry and adds appropriate formatting diff --git a/ls/src/main.rs b/ls/src/main.rs index a9b530b0..b564ff4b 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -86,11 +86,21 @@ fn main() -> io::Result<()> { } if !flags.comma_separate && flags.show_list() { - if print_list(dir, &mut writer, flags).is_err() { - exit_code = 1 + match print_list(dir, &mut writer, flags) { + Ok(_) => {}, + Err(err) => { + eprintln!("ls: cannot access '{}': {}", file, err); + exit_code = 1; + }, + } + } else { + match print_default(dir, &mut writer, flags) { + Ok(_) => {}, + Err(err) => { + eprintln!("ls: cannot access '{}': {}", file, err); + exit_code = 1; + }, } - } else if print_default(dir, &mut writer, flags).is_err() { - exit_code = 1; } }, Err(err) => { From 8eb26a6c19a90b4667e76f6e66fb8301628e1cca Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Thu, 29 Oct 2020 11:05:50 -0500 Subject: [PATCH 071/133] Ls: add comments --- ls/src/file.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ls/src/file.rs b/ls/src/file.rs index b4ba5ce1..19d59943 100644 --- a/ls/src/file.rs +++ b/ls/src/file.rs @@ -67,6 +67,7 @@ impl File { /// Retrieves the number of hard links pointing to a file as a string pub fn hard_links(&self) -> String { self.metadata.nlink().to_string() } + /// Retrieves the inode number as a string pub fn inode(&self) -> String { self.metadata.ino().to_string() } /// Retrieves the file's user name as a string. If the `-n` flag is set, @@ -142,6 +143,7 @@ impl File { result } + /// Checks if a string looks like a hidden unix file name pub fn is_hidden(name: &str) -> bool { name.starts_with('.') } /// Gets the file name from a `PathBuf` From 3a83d7d12e4791a8abe0a90e8a5f16e35cef9ffd Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Thu, 29 Oct 2020 11:21:49 -0500 Subject: [PATCH 072/133] Ls: remove panic --- ls/src/main.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/ls/src/main.rs b/ls/src/main.rs index b564ff4b..eea343e0 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -66,16 +66,14 @@ fn main() -> io::Result<()> { // be canonicalize incase the path is relative let current = path::PathBuf::from(file).canonicalize().unwrap(); - let dot = File::from_name(".".to_string(), current.clone(), flags) - .expect("Failed to read ."); + let dot = File::from_name(".".to_string(), current.clone(), flags)?; // Retrieve the parent path. Default to the current path if the parent doesn't // exist let parent_path = path::PathBuf::from(dot.path.parent().unwrap_or_else(|| current.as_path())); - let dot_dot = File::from_name("..".to_string(), parent_path, flags) - .expect("Failed to read .."); + let dot_dot = File::from_name("..".to_string(), parent_path, flags)?; dir.insert(0, dot); dir.insert(1, dot_dot); From ebdb64b8ebebf914d170f548afaf20a2385c159c Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Fri, 30 Oct 2020 10:40:11 -0500 Subject: [PATCH 073/133] Ls: retrieve values once --- ls/src/main.rs | 183 +++++++++++++++++++++++++++++-------------------- 1 file changed, 107 insertions(+), 76 deletions(-) diff --git a/ls/src/main.rs b/ls/src/main.rs index eea343e0..88f803cd 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -1,6 +1,7 @@ use std::{ fs, io::{self, Write}, + os::unix::fs::MetadataExt, path, process, string::String, time::SystemTime, @@ -133,135 +134,165 @@ fn print_default(files: Vec, writer: &mut W, flags: Flags) -> io Ok(()) } +struct Column { + pub alignment: Alignment, + width: *mut usize, + pub value: String, +} + +impl Column { + pub fn from(value: String, width: *mut usize, alignment: Alignment) -> Self { + Column { alignment, width, value } + } + + pub fn width(&self) -> usize { + unsafe { + *self.width + } + } +} + /// Prints information about the provided file in the long (`-l`) format fn print_list(files: Vec, writer: &mut W, flags: Flags) -> io::Result<()> { let mut inode_width = 1; let mut block_width = 1; + let mut permissions_width = 1; let mut hard_links_width = 1; let mut user_width = 1; let mut group_width = 1; let mut size_width = 1; + let mut time_width = 1; + let mut file_name_width = 1; + + let mut rows: Vec> = Vec::new(); for file in &files { + let mut row: Vec = Vec::new(); + + // Process the file's inode if flags.inode { - let inode = file.inode().len(); + let inode = file.inode(); + let inode_len = inode.len(); - if inode > inode_width { - inode_width = inode; + if inode_len > inode_width { + inode_width = inode_len; } + + let inode_width_ptr: *mut usize = &mut inode_width; + + row.push(Column::from(inode, inode_width_ptr, Alignment::Right)); } + // Process the file's block size if flags.size { - let block = file.blocks().len(); + let block = file.blocks(); + let block_len = block.len(); - if block > block_width { - block_width = block; + if block_len > block_width { + block_width = block_len; } + + let block_width_ptr: *mut usize = &mut block_width; + + row.push(Column::from(block, block_width_ptr, Alignment::Right)); } - let hard_links = file.hard_links().len(); + // Process the file's permissions + let permissions = file.permissions(); + + let permissions_width_ptr: *mut usize = &mut permissions_width; - if hard_links > hard_links_width { - hard_links_width = hard_links; + row.push(Column::from(permissions, permissions_width_ptr, Alignment::Left)); + + // Process the file's hard links + let hard_links = file.hard_links(); + let hard_links_len = hard_links.len(); + + if hard_links_len > hard_links_width { + hard_links_width = hard_links_len; } - let user: usize; + let hard_links_width_ptr: *mut usize = &mut hard_links_width; - match file.user() { + row.push(Column::from(hard_links, hard_links_width_ptr, Alignment::Right)); + + // Process the file's user name + let user = match file.user() { Ok(file_user) => { - user = file_user.len(); + file_user }, Err(err) => { eprintln!("ls: {}", err); - process::exit(1); + file.metadata.uid().to_string() }, - } + }; - if user > user_width { - user_width = user; + let user_len = user.len(); + + if user_len > user_width { + user_width = user_len; } - if !flags.no_owner { - let group: usize; + let user_width_ptr: *mut usize = &mut user_width; + + row.push(Column::from(user, user_width_ptr, Alignment::Left)); - match file.group() { + // Process the file's group name + if !flags.no_owner { + let group = match file.group() { Ok(file_group) => { - group = file_group.len(); + file_group }, Err(err) => { eprintln!("ls: {}", err); - process::exit(1); + file.metadata.gid().to_string() }, - } + }; - if group > group_width { - group_width = group; + let group_len = group.len(); + + if group_len > group_width { + group_width = group_len; } - } - let size = file.size().len(); + let group_width_ptr: *mut usize = &mut group_width; - if size > size_width { - size_width = size; + row.push(Column::from(group, group_width_ptr, Alignment::Left)); } - } - for file in &files { - if flags.inode { - write!( - writer, - "{} ", - file.inode().pad_to_width_with_alignment(inode_width, Alignment::Right) - )?; - } + // Process the file's size + let size = file.size(); + let size_len = file.size().len(); - if flags.size { - write!( - writer, - "{} ", - file.blocks().pad_to_width_with_alignment(block_width, Alignment::Right) - )?; + if size_len > size_width { + size_width = size_len; } - write!(writer, "{} ", file.permissions())?; + let size_width_ptr: *mut usize = &mut size_width; - write!( - writer, - "{} ", - file.hard_links().pad_to_width_with_alignment(hard_links_width, Alignment::Right) - )?; + row.push(Column::from(size, size_width_ptr, Alignment::Right)); - match file.user() { - Ok(user) => { - write!(writer, "{} ", user.pad_to_width(user_width))?; - }, - Err(err) => { - eprintln!("ls: {}", err); - process::exit(1); - }, - } + // Process the file's timestamp + let time_width_ptr: *mut usize = &mut time_width; - if !flags.no_owner { - match file.group() { - Ok(group) => { - write!(writer, "{} ", group.pad_to_width(group_width))?; - }, - Err(err) => { - eprintln!("ls: {}", err); - process::exit(1); - }, - } - } + row.push(Column::from(file.time()?, time_width_ptr, Alignment::Left)); - write!( - writer, - "{} ", - file.size().pad_to_width_with_alignment(size_width, Alignment::Right) - )?; + // Process the file's name + let file_name_width_ptr: *mut usize = &mut file_name_width; - write!(writer, "{} ", file.time()?)?; + row.push(Column::from(file.file_name(), file_name_width_ptr, Alignment::Left)); - write!(writer, "{}", file.file_name())?; + rows.push(row); + } + + for row in rows { + for column in row { + write!( + writer, + "{} ", + column.value.pad_to_width_with_alignment(column.width(), column.alignment) + )?; + } writeln!(writer)?; } From f62e547a6a2ed69104f59ad772f2d15554aa2183 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Fri, 30 Oct 2020 13:30:38 -0500 Subject: [PATCH 074/133] Ls: read individual files as well as directories --- ls/src/main.rs | 149 ++++++++++++++++++++++++++++--------------------- 1 file changed, 84 insertions(+), 65 deletions(-) diff --git a/ls/src/main.rs b/ls/src/main.rs index 88f803cd..ca3fe21a 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -30,82 +30,101 @@ fn main() -> io::Result<()> { let multiple = files.len() > 1; - for file in files { - match fs::read_dir(file) { - Ok(dir) => { - let mut dir: Vec<_> = dir - // Collect information about the file or directory - .map(|entry| File::from(entry.unwrap().path(), flags).unwrap()) - // Hide hidden files and directories if `-a` or `-A` flags - // weren't provided - .filter(|file| !File::is_hidden(&file.name) || flags.show_hidden()) - .collect(); - - if !flags.no_sort { - if flags.time { - if flags.last_accessed { - dir.sort_by_key(sort_by_access_time); + for (i, file) in files.enumerate() { + if multiple { + if i == 0 { + writeln!(writer, "{}:", file)?; + } else { + writeln!(writer, "\n{}:", file)?; + } + } + + let mut result: Vec; + + let path = path::PathBuf::from(file); + + if path.is_file() { + result = Vec::new(); + + let item = File::from(path::PathBuf::from(file), flags)?; + + result.push(item); + } else { + match fs::read_dir(file) { + Ok(dir) => { + result = dir + // Collect information about the file or directory + .map(|entry| File::from(entry.unwrap().path(), flags).unwrap()) + // Hide hidden files and directories if `-a` or `-A` flags + // weren't provided + .filter(|file| !File::is_hidden(&file.name) || flags.show_hidden()) + .collect(); + + if !flags.no_sort { + if flags.time { + if flags.last_accessed { + result.sort_by_key(sort_by_access_time); + } else { + result.sort_by_key(sort_by_time); + } + result.reverse(); + } else if flags.sort_size { + result.sort_by_key(sort_by_size); + result.reverse(); } else { - dir.sort_by_key(sort_by_time); + // Sort the directory entries by file name by default + result.sort_by_key(sort_by_name); } - dir.reverse(); - } else if flags.sort_size { - dir.sort_by_key(sort_by_size); - dir.reverse(); - } else { - // Sort the directory entries by file name by default - dir.sort_by_key(sort_by_name); - } - if flags.reverse { - dir.reverse(); + if flags.reverse { + result.reverse(); + } } - } + }, + Err(err) => { + eprintln!("ls: cannot access '{}': {}", file, err); + exit_code = 1; - if flags.all || flags.no_sort { - // Retrieve the current directories information. This must - // be canonicalize incase the path is relative - let current = path::PathBuf::from(file).canonicalize().unwrap(); + break; + }, - let dot = File::from_name(".".to_string(), current.clone(), flags)?; + } + } - // Retrieve the parent path. Default to the current path if the parent doesn't - // exist - let parent_path = - path::PathBuf::from(dot.path.parent().unwrap_or_else(|| current.as_path())); + if flags.all || flags.no_sort { + // Retrieve the current directories information. This must + // be canonicalize incase the path is relative + let current = path::PathBuf::from(file).canonicalize().unwrap(); - let dot_dot = File::from_name("..".to_string(), parent_path, flags)?; + let dot = File::from_name(".".to_string(), current.clone(), flags)?; - dir.insert(0, dot); - dir.insert(1, dot_dot); - } + // Retrieve the parent path. Default to the current path if the parent doesn't + // exist + let parent_path = + path::PathBuf::from(dot.path.parent().unwrap_or_else(|| current.as_path())); - if multiple { - writeln!(writer, "\n{}:", file)?; - } + let dot_dot = File::from_name("..".to_string(), parent_path, flags)?; - if !flags.comma_separate && flags.show_list() { - match print_list(dir, &mut writer, flags) { - Ok(_) => {}, - Err(err) => { - eprintln!("ls: cannot access '{}': {}", file, err); - exit_code = 1; - }, - } - } else { - match print_default(dir, &mut writer, flags) { - Ok(_) => {}, - Err(err) => { - eprintln!("ls: cannot access '{}': {}", file, err); - exit_code = 1; - }, - } - } - }, - Err(err) => { - eprintln!("ls: cannot access '{}': {}", file, err); - exit_code = 1; - }, + result.insert(0, dot); + result.insert(1, dot_dot); + } + + if !flags.comma_separate && flags.show_list() { + match print_list(result, &mut writer, flags) { + Ok(_) => {}, + Err(err) => { + eprintln!("ls: cannot access '{}': {}", file, err); + exit_code = 1; + }, + } + } else { + match print_default(result, &mut writer, flags) { + Ok(_) => {}, + Err(err) => { + eprintln!("ls: cannot access '{}': {}", file, err); + exit_code = 1; + }, + } } } From 31cce2a35ef4e66f45efb288d15575e554382600 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Sat, 31 Oct 2020 15:31:55 -0500 Subject: [PATCH 075/133] Ls: calculate the total --- ls/src/main.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/ls/src/main.rs b/ls/src/main.rs index ca3fe21a..b1d9b777 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -45,7 +45,7 @@ fn main() -> io::Result<()> { if path.is_file() { result = Vec::new(); - + let item = File::from(path::PathBuf::from(file), flags)?; result.push(item); @@ -94,7 +94,7 @@ fn main() -> io::Result<()> { if flags.all || flags.no_sort { // Retrieve the current directories information. This must // be canonicalize incase the path is relative - let current = path::PathBuf::from(file).canonicalize().unwrap(); + let current = path::PathBuf::from(file).canonicalize()?; let dot = File::from_name(".".to_string(), current.clone(), flags)?; @@ -185,6 +185,8 @@ fn print_list(files: Vec, writer: &mut W, flags: Flags) -> io::R let mut rows: Vec> = Vec::new(); + let mut total: u64 = 0; + for file in &files { let mut row: Vec = Vec::new(); @@ -202,6 +204,8 @@ fn print_list(files: Vec, writer: &mut W, flags: Flags) -> io::R row.push(Column::from(inode, inode_width_ptr, Alignment::Right)); } + total += file.metadata.blocks(); + // Process the file's block size if flags.size { let block = file.blocks(); @@ -304,6 +308,8 @@ fn print_list(files: Vec, writer: &mut W, flags: Flags) -> io::R rows.push(row); } + writeln!(writer, "total {}", total)?; + for row in rows { for column in row { write!( From ef2aa7f2cfe73e95aa13742ca8d58376ef822c08 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Mon, 2 Nov 2020 09:36:36 -0600 Subject: [PATCH 076/133] Ls: clean up --- ls/src/main.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ls/src/main.rs b/ls/src/main.rs index b1d9b777..e3d81d50 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -33,10 +33,10 @@ fn main() -> io::Result<()> { for (i, file) in files.enumerate() { if multiple { if i == 0 { - writeln!(writer, "{}:", file)?; - } else { - writeln!(writer, "\n{}:", file)?; + writeln!(writer, "\n")?; } + + writeln!(writer, "{}:", file)?; } let mut result: Vec; From 4ce77d73a672c772d3a00048322913cf5835ecd1 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Mon, 2 Nov 2020 09:38:10 -0600 Subject: [PATCH 077/133] Ls: format code --- ls/src/main.rs | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/ls/src/main.rs b/ls/src/main.rs index e3d81d50..3e6135ae 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -87,7 +87,6 @@ fn main() -> io::Result<()> { break; }, - } } @@ -164,11 +163,7 @@ impl Column { Column { alignment, width, value } } - pub fn width(&self) -> usize { - unsafe { - *self.width - } - } + pub fn width(&self) -> usize { unsafe { *self.width } } } /// Prints information about the provided file in the long (`-l`) format @@ -241,9 +236,7 @@ fn print_list(files: Vec, writer: &mut W, flags: Flags) -> io::R // Process the file's user name let user = match file.user() { - Ok(file_user) => { - file_user - }, + Ok(file_user) => file_user, Err(err) => { eprintln!("ls: {}", err); file.metadata.uid().to_string() @@ -263,9 +256,7 @@ fn print_list(files: Vec, writer: &mut W, flags: Flags) -> io::R // Process the file's group name if !flags.no_owner { let group = match file.group() { - Ok(file_group) => { - file_group - }, + Ok(file_group) => file_group, Err(err) => { eprintln!("ls: {}", err); file.metadata.gid().to_string() From b7420c585f24983c6a6278ecb542de5391af6c27 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Mon, 2 Nov 2020 09:59:27 -0600 Subject: [PATCH 078/133] Ls: return current time rather than panic --- ls/src/main.rs | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/ls/src/main.rs b/ls/src/main.rs index 3e6135ae..66c64e9e 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -317,7 +317,17 @@ fn print_list(files: Vec, writer: &mut W, flags: Flags) -> io::R } /// Sort a list of files by last accessed time -fn sort_by_access_time(file: &File) -> SystemTime { file.metadata.accessed().unwrap() } +fn sort_by_access_time(file: &File) -> SystemTime { + let accessed = file.metadata.accessed(); + + match accessed { + Ok(accessed) => accessed, + Err(err) => { + eprintln!("ls: {}", err); + SystemTime::now() + } + } +} /// Sort a list of files by file name alphabetically fn sort_by_name(file: &File) -> String { file.name.to_lowercase() } @@ -326,4 +336,14 @@ fn sort_by_name(file: &File) -> String { file.name.to_lowercase() } fn sort_by_size(file: &File) -> u64 { file.metadata.len() } /// Sort a list of directories by modification time -fn sort_by_time(file: &File) -> SystemTime { file.metadata.modified().unwrap() } +fn sort_by_time(file: &File) -> SystemTime { + let modified = file.metadata.modified(); + + match modified { + Ok(modified) => modified, + Err(err) => { + eprintln!("ls: {}", err); + SystemTime::now() + } + } +} From 4484cfcd141db8d97b29d6c2a72649994be1902e Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Mon, 2 Nov 2020 10:08:03 -0600 Subject: [PATCH 079/133] Ls: remove panic --- ls/src/file.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ls/src/file.rs b/ls/src/file.rs index 19d59943..1c05ef6f 100644 --- a/ls/src/file.rs +++ b/ls/src/file.rs @@ -199,7 +199,7 @@ impl File { let symlink = fs::read_link(self.path.clone()); if let Ok(symlink) = symlink { - let mut symlink_name = String::from(symlink.to_str().unwrap()); + let mut symlink_name = symlink.to_string_lossy().to_string(); if File::is_executable(&symlink) { symlink_name = self.add_executable_color(symlink_name); From 8c42c181d9e6bf69b99f420453909342d67f7330 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Mon, 2 Nov 2020 10:23:21 -0600 Subject: [PATCH 080/133] Ls: format code --- ls/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ls/src/main.rs b/ls/src/main.rs index 66c64e9e..e930d74e 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -325,7 +325,7 @@ fn sort_by_access_time(file: &File) -> SystemTime { Err(err) => { eprintln!("ls: {}", err); SystemTime::now() - } + }, } } @@ -344,6 +344,6 @@ fn sort_by_time(file: &File) -> SystemTime { Err(err) => { eprintln!("ls: {}", err); SystemTime::now() - } + }, } } From e37f135dc644ad85847de7b30e2ccb890216daad Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Mon, 2 Nov 2020 14:15:11 -0600 Subject: [PATCH 081/133] Ls: ignore . when sorting by name --- ls/src/main.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ls/src/main.rs b/ls/src/main.rs index e930d74e..a82ea266 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -330,7 +330,9 @@ fn sort_by_access_time(file: &File) -> SystemTime { } /// Sort a list of files by file name alphabetically -fn sort_by_name(file: &File) -> String { file.name.to_lowercase() } +fn sort_by_name(file: &File) -> String { + file.name.to_lowercase().trim_start_matches('.').to_string() +} /// Sort a list of files by size fn sort_by_size(file: &File) -> u64 { file.metadata.len() } From b4283fca66e47e5397444a982be9ce6ba7cc9f86 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Mon, 2 Nov 2020 14:17:03 -0600 Subject: [PATCH 082/133] Ls: add periods --- ls/src/cli.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/ls/src/cli.rs b/ls/src/cli.rs index c5297e50..29cbae65 100644 --- a/ls/src/cli.rs +++ b/ls/src/cli.rs @@ -31,7 +31,7 @@ pub(crate) fn create_app<'a, 'b>() -> App<'a, 'b> { Arg::with_name("almost_all") .help( "Write out all directory entries, including those whose names begin with a \ - ( '.' ) but excluding the entries dot and dot-dot (if they exist)", + ( '.' ) but excluding the entries dot and dot-dot (if they exist).", ) .short("A") .long("almost-all"), @@ -47,13 +47,13 @@ pub(crate) fn create_app<'a, 'b>() -> App<'a, 'b> { ) .arg( Arg::with_name("classify") - .help("Append indicator (one of */=>@|) to entries") + .help("Append indicator (one of */=>@|) to entries.") .short("F") .long("classify"), ) .arg( Arg::with_name("comma_separate") - .help("Fill width with a comma separated list of entries") + .help("Fill width with a comma separated list of entries.") .short("m") .long("comma_separate"), ) @@ -61,20 +61,20 @@ pub(crate) fn create_app<'a, 'b>() -> App<'a, 'b> { Arg::with_name("dereference") .help( "When showing file information for a symbolic link, show information for the \ - file the link references rather than for the link itself", + file the link references rather than for the link itself.", ) .short("L") .long("dereference"), ) .arg( Arg::with_name("indicator") - .help("Write a ( '/' ) after each filename if that file is a directory") + .help("Write a ( '/' ) after each filename if that file is a directory.") .short("p") .long("indicator"), ) .arg( Arg::with_name("inode") - .help("For each file, write the file's file serial number") + .help("For each file, write the file's file serial number.") .short("i") .long("inode"), ) @@ -82,7 +82,7 @@ pub(crate) fn create_app<'a, 'b>() -> App<'a, 'b> { Arg::with_name("last_accessed") .help( "Use time of last access instead of last modification of the file for sorting \ - -t or writing -l", + -t or writing -l.", ) .short("u") .long("last-accessed"), @@ -90,37 +90,37 @@ pub(crate) fn create_app<'a, 'b>() -> App<'a, 'b> { .arg(Arg::with_name("list").help("Use a long listing format").short("l").long("list")) .arg( Arg::with_name("no_owner") - .help("Like -l, but do not list owner") + .help("Like -l, but do not list owner.") .short("g") .long("no-owner"), ) .arg( Arg::with_name("numeric_uid_gid") - .help("Like -l, but list numeric user and group IDs") + .help("Like -l, but list numeric user and group IDs.") .short("n") .long("numeric-uid-gid"), ) .arg( Arg::with_name("reverse") - .help("Reverse order while sorting") + .help("Reverse order while sorting.") .short("r") .long("reverse"), ) .arg( Arg::with_name("size") - .help("Print the allocated size of each file, in blocks") + .help("Print the allocated size of each file, in blocks.") .short("s") .long("size"), ) .arg( Arg::with_name("sort_size") - .help("Sort by first file size, largest first") + .help("Sort by first file size, largest first.") .short("S") .long("sort-size"), ) .arg( Arg::with_name("time") - .help("Sort by modification time, newest first") + .help("Sort by modification time, newest first.") .short("t") .long("time"), ) From 867c941aef4c721dd57d5d5dc85db8d725666978 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Tue, 3 Nov 2020 09:23:19 -0600 Subject: [PATCH 083/133] Ls: remove unneeded crate --- ls/Cargo.toml | 1 - ls/src/main.rs | 18 +++++++++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/ls/Cargo.toml b/ls/Cargo.toml index 1dbc04fc..382d6cb7 100644 --- a/ls/Cargo.toml +++ b/ls/Cargo.toml @@ -15,7 +15,6 @@ coreutils_core = { path = "../coreutils_core" } unix_mode = "0.1.1" chrono = "0.4" ansi_term = "0.12.1" -pad = "0.1.6" [build-dependencies] clap = "^2.33.0" diff --git a/ls/src/main.rs b/ls/src/main.rs index a82ea266..d560fe2d 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -7,8 +7,6 @@ use std::{ time::SystemTime, }; -use pad::{Alignment, PadStr}; - extern crate chrono; mod cli; @@ -152,6 +150,12 @@ fn print_default(files: Vec, writer: &mut W, flags: Flags) -> io Ok(()) } +#[derive(PartialEq, Eq)] +enum Alignment { + Left, + Right, +} + struct Column { pub alignment: Alignment, width: *mut usize, @@ -303,11 +307,11 @@ fn print_list(files: Vec, writer: &mut W, flags: Flags) -> io::R for row in rows { for column in row { - write!( - writer, - "{} ", - column.value.pad_to_width_with_alignment(column.width(), column.alignment) - )?; + if column.alignment == Alignment::Left { + write!(writer, "{:<1$} ", column.value, column.width())?; + } else if column.alignment == Alignment::Right { + write!(writer, "{:>1$} ", column.value, column.width())?; + } } writeln!(writer)?; From 3cd23eb8fcd36d211fc1add1335b1b1cfc336216 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Tue, 3 Nov 2020 09:40:37 -0600 Subject: [PATCH 084/133] Ls: switch to BufWriter --- ls/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ls/src/main.rs b/ls/src/main.rs index d560fe2d..51c3a9e6 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -1,6 +1,6 @@ use std::{ fs, - io::{self, Write}, + io::{self, BufWriter, Write}, os::unix::fs::MetadataExt, path, process, string::String, @@ -24,7 +24,7 @@ fn main() -> io::Result<()> { let mut exit_code = 0; - let mut writer: Box = Box::new(io::stdout()); + let mut writer = BufWriter::new(io::stdout()); let multiple = files.len() > 1; From d97d9a5fc4db25159031687436f23239b96ab9c1 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Tue, 3 Nov 2020 13:25:56 -0600 Subject: [PATCH 085/133] Ls: implement -k flag --- ls/src/cli.rs | 9 +++++++++ ls/src/file.rs | 12 +++++++++++- ls/src/flags.rs | 3 +++ ls/src/main.rs | 8 ++++---- 4 files changed, 27 insertions(+), 5 deletions(-) diff --git a/ls/src/cli.rs b/ls/src/cli.rs index 29cbae65..81cbac81 100644 --- a/ls/src/cli.rs +++ b/ls/src/cli.rs @@ -51,6 +51,15 @@ pub(crate) fn create_app<'a, 'b>() -> App<'a, 'b> { .short("F") .long("classify"), ) + .arg( + Arg::with_name("block_size") + .help( + "Set the block size for the -s option and the per-directory block count \ + written for the -l, -n, -s, -g, and -o options to 1024 bytes.", + ) + .short("k") + .long("block_size"), + ) .arg( Arg::with_name("comma_separate") .help("Fill width with a comma separated list of entries.") diff --git a/ls/src/file.rs b/ls/src/file.rs index 1c05ef6f..ef646248 100644 --- a/ls/src/file.rs +++ b/ls/src/file.rs @@ -55,7 +55,17 @@ impl File { } /// Retrieves the number of blocks allocated to a file as a string - pub fn blocks(&self) -> String { self.metadata.blocks().to_string() } + pub fn blocks(&self) -> u64 { + let blocks = self.metadata.blocks(); + + if self.flags.block_size { + let st_size = blocks * 512; + + st_size / 1024 + } else { + blocks + } + } /// Retrieves a files permissions as a string pub fn permissions(&self) -> String { diff --git a/ls/src/flags.rs b/ls/src/flags.rs index 79756d5a..80d5b5a6 100644 --- a/ls/src/flags.rs +++ b/ls/src/flags.rs @@ -5,6 +5,7 @@ use clap::ArgMatches; pub(crate) struct Flags { pub all: bool, pub almost_all: bool, + pub block_size: bool, pub classify: bool, pub comma_separate: bool, pub dereference: bool, @@ -26,6 +27,7 @@ impl Flags { pub fn from_matches(matches: &ArgMatches<'_>) -> Self { let all = matches.is_present("all"); let almost_all = matches.is_present("almost_all"); + let block_size = matches.is_present("block_size"); let classify = matches.is_present("classify"); let comma_separate = matches.is_present("comma_separate"); let dereference = matches.is_present("dereference"); @@ -44,6 +46,7 @@ impl Flags { Flags { all, almost_all, + block_size, classify, comma_separate, dereference, diff --git a/ls/src/main.rs b/ls/src/main.rs index 51c3a9e6..613431c0 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -203,12 +203,12 @@ fn print_list(files: Vec, writer: &mut W, flags: Flags) -> io::R row.push(Column::from(inode, inode_width_ptr, Alignment::Right)); } - total += file.metadata.blocks(); + total += file.blocks(); // Process the file's block size if flags.size { - let block = file.blocks(); - let block_len = block.len(); + let block = file.blocks() as usize; + let block_len = block.to_string().len(); if block_len > block_width { block_width = block_len; @@ -216,7 +216,7 @@ fn print_list(files: Vec, writer: &mut W, flags: Flags) -> io::R let block_width_ptr: *mut usize = &mut block_width; - row.push(Column::from(block, block_width_ptr, Alignment::Right)); + row.push(Column::from(block.to_string(), block_width_ptr, Alignment::Right)); } // Process the file's permissions From 008e433c25c3e5e493ca7f91b2986f9754f1f639 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Tue, 3 Nov 2020 14:00:10 -0600 Subject: [PATCH 086/133] Ls: remove return type from main --- ls/src/main.rs | 62 ++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 52 insertions(+), 10 deletions(-) diff --git a/ls/src/main.rs b/ls/src/main.rs index 613431c0..4a49f43e 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -16,7 +16,7 @@ mod flags; use file::File; use flags::Flags; -fn main() -> io::Result<()> { +fn main() { let matches = cli::create_app().get_matches(); let files = matches.values_of("FILE").unwrap(); @@ -31,10 +31,22 @@ fn main() -> io::Result<()> { for (i, file) in files.enumerate() { if multiple { if i == 0 { - writeln!(writer, "\n")?; + match writeln!(writer, "\n") { + Ok(_) => {}, + Err(err) => { + eprintln!("ls: {}", err); + process::exit(1); + } + } } - writeln!(writer, "{}:", file)?; + match writeln!(writer, "{}:", file) { + Ok(_) => {}, + Err(err) => { + eprintln!("ls: {}", err); + process::exit(1); + } + } } let mut result: Vec; @@ -44,9 +56,17 @@ fn main() -> io::Result<()> { if path.is_file() { result = Vec::new(); - let item = File::from(path::PathBuf::from(file), flags)?; + let item = File::from(path::PathBuf::from(file), flags); - result.push(item); + match item { + Ok(item) => { + result.push(item); + }, + Err(err) => { + eprintln!("ls: cannot access {}: {}", err, file); + process::exit(1); + } + } } else { match fs::read_dir(file) { Ok(dir) => { @@ -91,16 +111,40 @@ fn main() -> io::Result<()> { if flags.all || flags.no_sort { // Retrieve the current directories information. This must // be canonicalize incase the path is relative - let current = path::PathBuf::from(file).canonicalize()?; + let current = path::PathBuf::from(file).canonicalize(); + + let current = match current { + Ok(current) => current, + Err(err) => { + eprintln!("ls: {}", err); + process::exit(1); + } + }; + + let dot = File::from_name(".".to_string(), current.clone(), flags); - let dot = File::from_name(".".to_string(), current.clone(), flags)?; + let dot = match dot { + Ok(dot) => dot, + Err(err) => { + eprintln!("ls: {}", err); + process::exit(1); + } + }; // Retrieve the parent path. Default to the current path if the parent doesn't // exist let parent_path = path::PathBuf::from(dot.path.parent().unwrap_or_else(|| current.as_path())); - let dot_dot = File::from_name("..".to_string(), parent_path, flags)?; + let dot_dot = File::from_name("..".to_string(), parent_path, flags); + + let dot_dot = match dot_dot { + Ok(dot_dot) => dot_dot, + Err(err) => { + eprintln!("ls: {}", err); + process::exit(1); + } + }; result.insert(0, dot); result.insert(1, dot_dot); @@ -128,8 +172,6 @@ fn main() -> io::Result<()> { if exit_code != 0 { process::exit(exit_code); } - - Ok(()) } /// Prints information about a file in the default format From 1005f7c850c9b1c1c899c6838a60a1b9947a5e5b Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Tue, 3 Nov 2020 14:00:41 -0600 Subject: [PATCH 087/133] Ls: run cargo fmt --- ls/src/main.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ls/src/main.rs b/ls/src/main.rs index 4a49f43e..fa8ea3e1 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -36,7 +36,7 @@ fn main() { Err(err) => { eprintln!("ls: {}", err); process::exit(1); - } + }, } } @@ -45,7 +45,7 @@ fn main() { Err(err) => { eprintln!("ls: {}", err); process::exit(1); - } + }, } } @@ -65,7 +65,7 @@ fn main() { Err(err) => { eprintln!("ls: cannot access {}: {}", err, file); process::exit(1); - } + }, } } else { match fs::read_dir(file) { @@ -118,7 +118,7 @@ fn main() { Err(err) => { eprintln!("ls: {}", err); process::exit(1); - } + }, }; let dot = File::from_name(".".to_string(), current.clone(), flags); @@ -128,7 +128,7 @@ fn main() { Err(err) => { eprintln!("ls: {}", err); process::exit(1); - } + }, }; // Retrieve the parent path. Default to the current path if the parent doesn't @@ -143,7 +143,7 @@ fn main() { Err(err) => { eprintln!("ls: {}", err); process::exit(1); - } + }, }; result.insert(0, dot); From 89c353a138fb2e23ebb7f1bb7ce52878975e8a4f Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Tue, 3 Nov 2020 15:48:53 -0600 Subject: [PATCH 088/133] Ls: implement -c flag --- ls/src/cli.rs | 9 +++++++++ ls/src/file.rs | 16 +++++++++++----- ls/src/flags.rs | 3 +++ ls/src/main.rs | 30 +++++++----------------------- 4 files changed, 30 insertions(+), 28 deletions(-) diff --git a/ls/src/cli.rs b/ls/src/cli.rs index 81cbac81..c2477dca 100644 --- a/ls/src/cli.rs +++ b/ls/src/cli.rs @@ -36,6 +36,15 @@ pub(crate) fn create_app<'a, 'b>() -> App<'a, 'b> { .short("A") .long("almost-all"), ) + .arg( + Arg::with_name("file_status_modification") + .help( + "Use time of last modification of the file status information instead of last \ + modification of the file itself for sorting -t or writing -l.", + ) + .short("c") + .long("file_status_modification"), + ) .arg( Arg::with_name("no_sort") .help( diff --git a/ls/src/file.rs b/ls/src/file.rs index ef646248..92340315 100644 --- a/ls/src/file.rs +++ b/ls/src/file.rs @@ -15,7 +15,7 @@ use ansi_term::Color; extern crate chrono; -use chrono::{DateTime, Local}; +use chrono::{DateTime, Local, TimeZone}; use crate::flags::Flags; @@ -117,13 +117,19 @@ impl File { /// `%b %e %H:%M` unless the duration is greater than six months, which case /// the date format will be `%b %e %Y`. pub fn time(&self) -> io::Result { - let system_time = if self.flags.last_accessed { - self.metadata.accessed()? + let (secs, nsecs) = if self.flags.last_accessed { + // Retrieve the files last accessed time + (self.metadata.atime(), self.metadata.atime_nsec() as u32) + } else if self.flags.file_status_modification { + // Retrieve the files last modification time of the status + // information + (self.metadata.ctime(), self.metadata.ctime_nsec() as u32) } else { - self.metadata.modified()? + // Retrieve the files modification time + (self.metadata.mtime(), self.metadata.mtime_nsec() as u32) }; - let datetime: DateTime = system_time.into(); + let datetime: DateTime = Local.timestamp(secs, nsecs); let mut fmt = "%b %e %H:%M"; diff --git a/ls/src/flags.rs b/ls/src/flags.rs index 80d5b5a6..996572c8 100644 --- a/ls/src/flags.rs +++ b/ls/src/flags.rs @@ -9,6 +9,7 @@ pub(crate) struct Flags { pub classify: bool, pub comma_separate: bool, pub dereference: bool, + pub file_status_modification: bool, pub indicator: bool, pub inode: bool, pub last_accessed: bool, @@ -31,6 +32,7 @@ impl Flags { let classify = matches.is_present("classify"); let comma_separate = matches.is_present("comma_separate"); let dereference = matches.is_present("dereference"); + let file_status_modification = matches.is_present("file_status_modification"); let indicator = matches.is_present("indicator"); let inode = matches.is_present("inode"); let last_accessed = matches.is_present("last_accessed"); @@ -50,6 +52,7 @@ impl Flags { classify, comma_separate, dereference, + file_status_modification, inode, indicator, last_accessed, diff --git a/ls/src/main.rs b/ls/src/main.rs index fa8ea3e1..9f7adb59 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -4,7 +4,6 @@ use std::{ os::unix::fs::MetadataExt, path, process, string::String, - time::SystemTime, }; extern crate chrono; @@ -82,6 +81,8 @@ fn main() { if flags.time { if flags.last_accessed { result.sort_by_key(sort_by_access_time); + } else if flags.file_status_modification { + result.sort_by_key(sort_by_ctime) } else { result.sort_by_key(sort_by_time); } @@ -363,17 +364,10 @@ fn print_list(files: Vec, writer: &mut W, flags: Flags) -> io::R } /// Sort a list of files by last accessed time -fn sort_by_access_time(file: &File) -> SystemTime { - let accessed = file.metadata.accessed(); - - match accessed { - Ok(accessed) => accessed, - Err(err) => { - eprintln!("ls: {}", err); - SystemTime::now() - }, - } -} +fn sort_by_access_time(file: &File) -> i64 { file.metadata.atime() } + +/// Sort a list of files by last change of file status information +fn sort_by_ctime(file: &File) -> i64 { file.metadata.ctime() } /// Sort a list of files by file name alphabetically fn sort_by_name(file: &File) -> String { @@ -384,14 +378,4 @@ fn sort_by_name(file: &File) -> String { fn sort_by_size(file: &File) -> u64 { file.metadata.len() } /// Sort a list of directories by modification time -fn sort_by_time(file: &File) -> SystemTime { - let modified = file.metadata.modified(); - - match modified { - Ok(modified) => modified, - Err(err) => { - eprintln!("ls: {}", err); - SystemTime::now() - }, - } -} +fn sort_by_time(file: &File) -> i64 { file.metadata.mtime() } From 86c9852fb213a1f8f5eaf18fd53312fdf5f174d9 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Thu, 5 Nov 2020 10:03:43 -0600 Subject: [PATCH 089/133] Ls: refactor column calculation --- ls/src/main.rs | 98 ++++++++++++++++++++----------------------------- ls/src/table.rs | 46 +++++++++++++++++++++++ 2 files changed, 85 insertions(+), 59 deletions(-) create mode 100644 ls/src/table.rs diff --git a/ls/src/main.rs b/ls/src/main.rs index 9f7adb59..a4fa4006 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -11,9 +11,11 @@ extern crate chrono; mod cli; mod file; mod flags; +mod table; use file::File; use flags::Flags; +use table::{Row, Table}; fn main() { let matches = cli::create_app().get_matches(); @@ -193,44 +195,24 @@ fn print_default(files: Vec, writer: &mut W, flags: Flags) -> io Ok(()) } -#[derive(PartialEq, Eq)] -enum Alignment { - Left, - Right, -} - -struct Column { - pub alignment: Alignment, - width: *mut usize, - pub value: String, -} - -impl Column { - pub fn from(value: String, width: *mut usize, alignment: Alignment) -> Self { - Column { alignment, width, value } - } - - pub fn width(&self) -> usize { unsafe { *self.width } } -} - /// Prints information about the provided file in the long (`-l`) format fn print_list(files: Vec, writer: &mut W, flags: Flags) -> io::Result<()> { let mut inode_width = 1; let mut block_width = 1; - let mut permissions_width = 1; + let permissions_width = 1; let mut hard_links_width = 1; let mut user_width = 1; let mut group_width = 1; let mut size_width = 1; - let mut time_width = 1; - let mut file_name_width = 1; + let time_width = 1; + let file_name_width = 1; - let mut rows: Vec> = Vec::new(); + let mut rows = Table::new(); let mut total: u64 = 0; for file in &files { - let mut row: Vec = Vec::new(); + let mut row = Row::new(); // Process the file's inode if flags.inode { @@ -241,9 +223,7 @@ fn print_list(files: Vec, writer: &mut W, flags: Flags) -> io::R inode_width = inode_len; } - let inode_width_ptr: *mut usize = &mut inode_width; - - row.push(Column::from(inode, inode_width_ptr, Alignment::Right)); + row.inode = inode; } total += file.blocks(); @@ -257,17 +237,13 @@ fn print_list(files: Vec, writer: &mut W, flags: Flags) -> io::R block_width = block_len; } - let block_width_ptr: *mut usize = &mut block_width; - - row.push(Column::from(block.to_string(), block_width_ptr, Alignment::Right)); + row.block = block.to_string(); } // Process the file's permissions let permissions = file.permissions(); - let permissions_width_ptr: *mut usize = &mut permissions_width; - - row.push(Column::from(permissions, permissions_width_ptr, Alignment::Left)); + row.permissions = permissions; // Process the file's hard links let hard_links = file.hard_links(); @@ -277,9 +253,7 @@ fn print_list(files: Vec, writer: &mut W, flags: Flags) -> io::R hard_links_width = hard_links_len; } - let hard_links_width_ptr: *mut usize = &mut hard_links_width; - - row.push(Column::from(hard_links, hard_links_width_ptr, Alignment::Right)); + row.hard_links = hard_links; // Process the file's user name let user = match file.user() { @@ -296,9 +270,7 @@ fn print_list(files: Vec, writer: &mut W, flags: Flags) -> io::R user_width = user_len; } - let user_width_ptr: *mut usize = &mut user_width; - - row.push(Column::from(user, user_width_ptr, Alignment::Left)); + row.user = user; // Process the file's group name if !flags.no_owner { @@ -316,9 +288,7 @@ fn print_list(files: Vec, writer: &mut W, flags: Flags) -> io::R group_width = group_len; } - let group_width_ptr: *mut usize = &mut group_width; - - row.push(Column::from(group, group_width_ptr, Alignment::Left)); + row.group = group; } // Process the file's size @@ -329,34 +299,44 @@ fn print_list(files: Vec, writer: &mut W, flags: Flags) -> io::R size_width = size_len; } - let size_width_ptr: *mut usize = &mut size_width; - - row.push(Column::from(size, size_width_ptr, Alignment::Right)); + row.size = size; // Process the file's timestamp - let time_width_ptr: *mut usize = &mut time_width; - - row.push(Column::from(file.time()?, time_width_ptr, Alignment::Left)); + row.time = file.time()?; // Process the file's name - let file_name_width_ptr: *mut usize = &mut file_name_width; - - row.push(Column::from(file.file_name(), file_name_width_ptr, Alignment::Left)); + row.file_name = file.file_name(); rows.push(row); } writeln!(writer, "total {}", total)?; - for row in rows { - for column in row { - if column.alignment == Alignment::Left { - write!(writer, "{:<1$} ", column.value, column.width())?; - } else if column.alignment == Alignment::Right { - write!(writer, "{:>1$} ", column.value, column.width())?; - } + for row in rows.rows { + if flags.inode { + write!(writer, "{:>1$} ", row.inode, inode_width)?; + } + + if flags.size { + write!(writer, "{:>1$} ", row.block, block_width)?; } + write!(writer, "{:<1$} ", row.permissions, permissions_width)?; + + write!(writer, "{:>1$} ", row.hard_links, hard_links_width)?; + + write!(writer, "{:<1$} ", row.user, user_width)?; + + if !flags.no_owner { + write!(writer, "{:<1$} ", row.group, group_width)?; + } + + write!(writer, "{:>1$} ", row.size, size_width)?; + + write!(writer, "{:<1$} ", row.time, time_width)?; + + write!(writer, "{:<1$} ", row.file_name, file_name_width)?; + writeln!(writer)?; } diff --git a/ls/src/table.rs b/ls/src/table.rs new file mode 100644 index 00000000..9254110d --- /dev/null +++ b/ls/src/table.rs @@ -0,0 +1,46 @@ +/// Contains each row displayed in the -l` option. +pub(crate) struct Table { + pub rows: Vec, +} + +impl Table { + /// Initialize a new table + pub fn new() -> Self { + let rows = Vec::new(); + + Table { rows } + } + + /// Add a row the list of table. + pub fn push(&mut self, row: Row) { self.rows.push(row) } +} + +/// Contains each column displayed in the `-l` option. +pub(crate) struct Row { + pub inode: String, + pub block: String, + pub permissions: String, + pub hard_links: String, + pub user: String, + pub group: String, + pub size: String, + pub time: String, + pub file_name: String, +} + +impl Row { + /// Initialize a new row + pub fn new() -> Self { + let inode = String::new(); + let block = String::new(); + let permissions = String::new(); + let hard_links = String::new(); + let user = String::new(); + let group = String::new(); + let size = String::new(); + let time = String::new(); + let file_name = String::new(); + + Row { inode, block, permissions, hard_links, user, group, size, time, file_name } + } +} From 16b3eb1392315fd853e1f1d354f6a736a44568b7 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Thu, 5 Nov 2020 12:56:10 -0600 Subject: [PATCH 090/133] Ls: remove unneeded result --- ls/src/file.rs | 12 ++++++------ ls/src/main.rs | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ls/src/file.rs b/ls/src/file.rs index 92340315..8fce75f8 100644 --- a/ls/src/file.rs +++ b/ls/src/file.rs @@ -116,20 +116,20 @@ impl File { /// modified time of the file's status information. The date format used is /// `%b %e %H:%M` unless the duration is greater than six months, which case /// the date format will be `%b %e %Y`. - pub fn time(&self) -> io::Result { + pub fn time(&self) -> String { let (secs, nsecs) = if self.flags.last_accessed { // Retrieve the files last accessed time - (self.metadata.atime(), self.metadata.atime_nsec() as u32) + (self.metadata.atime(), self.metadata.atime_nsec()) } else if self.flags.file_status_modification { // Retrieve the files last modification time of the status // information - (self.metadata.ctime(), self.metadata.ctime_nsec() as u32) + (self.metadata.ctime(), self.metadata.ctime_nsec()) } else { // Retrieve the files modification time - (self.metadata.mtime(), self.metadata.mtime_nsec() as u32) + (self.metadata.mtime(), self.metadata.mtime_nsec()) }; - let datetime: DateTime = Local.timestamp(secs, nsecs); + let datetime: DateTime = Local.timestamp(secs, nsecs as u32); let mut fmt = "%b %e %H:%M"; @@ -143,7 +143,7 @@ impl File { fmt = "%b %e %Y"; } - Ok(datetime.format(fmt).to_string()) + datetime.format(fmt).to_string() } /// Check if a path is an executable file diff --git a/ls/src/main.rs b/ls/src/main.rs index a4fa4006..7e9c21c3 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -302,7 +302,7 @@ fn print_list(files: Vec, writer: &mut W, flags: Flags) -> io::R row.size = size; // Process the file's timestamp - row.time = file.time()?; + row.time = file.time(); // Process the file's name row.file_name = file.file_name(); From 4f0a9bfea5623e6fa1d98687e318a5af524d0d79 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Fri, 6 Nov 2020 12:03:06 -0600 Subject: [PATCH 091/133] Ls: get terminal dimensions --- coreutils_core/src/os/tty.rs | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/coreutils_core/src/os/tty.rs b/coreutils_core/src/os/tty.rs index 8ad72944..19888e45 100644 --- a/coreutils_core/src/os/tty.rs +++ b/coreutils_core/src/os/tty.rs @@ -7,7 +7,7 @@ use std::{ os::unix::io::AsRawFd, }; -use libc::ttyname; +use libc::{ioctl, ttyname, winsize, TIOCGWINSZ}; // use crate::file_descriptor::FileDescriptor; @@ -99,3 +99,30 @@ impl IsTTY for T { pub fn is_tty(file_descriptor: &impl AsRawFd) -> bool { unsafe { libc::isatty(file_descriptor.as_raw_fd()) == 1 } } + +/// Gets the width and height of a TTY. +/// +/// ## Example +/// ``` rust +/// use coreutils_core::os::tty::tty_dimensions; +/// let (width, height) = tty_dimensions(&std::io::stdout()).unwrap(); +/// ``` +#[inline] +pub fn tty_dimensions(file_descriptor: &impl AsRawFd) -> Option<(u16, u16)> { + if !is_tty(file_descriptor) { + return None; + } + + let mut size = winsize { + ws_row: 0, + ws_col: 0, + ws_xpixel: 0, + ws_ypixel: 0, + }; + + if unsafe { ioctl(file_descriptor.as_raw_fd(), TIOCGWINSZ, &mut size) } == -1 { + return None; + } + + Some((size.ws_col, size.ws_row)) +} From fd02d471f7b1171f95d2da0c2d53dc76905116a2 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Fri, 6 Nov 2020 12:03:19 -0600 Subject: [PATCH 092/133] Ls: implement -x flag --- ls/Cargo.toml | 1 + ls/src/cli.rs | 6 ++++++ ls/src/flags.rs | 6 +++++- ls/src/main.rs | 46 ++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 56 insertions(+), 3 deletions(-) diff --git a/ls/Cargo.toml b/ls/Cargo.toml index 382d6cb7..7d63725e 100644 --- a/ls/Cargo.toml +++ b/ls/Cargo.toml @@ -15,6 +15,7 @@ coreutils_core = { path = "../coreutils_core" } unix_mode = "0.1.1" chrono = "0.4" ansi_term = "0.12.1" +term_grid = "0.2.0" [build-dependencies] clap = "^2.33.0" diff --git a/ls/src/cli.rs b/ls/src/cli.rs index c2477dca..2e19d366 100644 --- a/ls/src/cli.rs +++ b/ls/src/cli.rs @@ -142,4 +142,10 @@ pub(crate) fn create_app<'a, 'b>() -> App<'a, 'b> { .short("t") .long("time"), ) + .arg( + Arg::with_name("order_left_to_right") + .help("Sort columns left to right.") + .short("x") + .long("order_left_to_right"), + ) } diff --git a/ls/src/flags.rs b/ls/src/flags.rs index 996572c8..8da04bc9 100644 --- a/ls/src/flags.rs +++ b/ls/src/flags.rs @@ -17,6 +17,7 @@ pub(crate) struct Flags { pub no_owner: bool, pub no_sort: bool, pub numeric_uid_gid: bool, + pub order_left_to_right: bool, pub reverse: bool, pub size: bool, pub sort_size: bool, @@ -40,6 +41,7 @@ impl Flags { let no_owner = matches.is_present("no_owner"); let no_sort = matches.is_present("no_sort"); let numeric_uid_gid = matches.is_present("numeric_uid_gid"); + let order_left_to_right = matches.is_present("order_left_to_right"); let reverse = matches.is_present("reverse"); let size = matches.is_present("size"); let sort_size = matches.is_present("sort_size"); @@ -60,6 +62,7 @@ impl Flags { no_owner, no_sort, numeric_uid_gid, + order_left_to_right, reverse, size, sort_size, @@ -69,7 +72,8 @@ impl Flags { /// Whether to print as a list based ont the provided flags pub fn show_list(&self) -> bool { - !self.comma_separate && self.list || self.no_owner || self.numeric_uid_gid + !(self.comma_separate || self.order_left_to_right) + && (self.list || self.no_owner || self.numeric_uid_gid) } /// Whether or not to show hidden files and directories diff --git a/ls/src/main.rs b/ls/src/main.rs index 7e9c21c3..4ada1f15 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -6,6 +6,9 @@ use std::{ string::String, }; +use coreutils_core::os::tty::{is_tty, tty_dimensions}; +use term_grid::{Alignment, Cell, Direction, Filling, Grid, GridOptions}; + extern crate chrono; mod cli; @@ -179,8 +182,18 @@ fn main() { /// Prints information about a file in the default format fn print_default(files: Vec, writer: &mut W, flags: Flags) -> io::Result<()> { - for file in files { - let file_name = file.file_name(); + if !is_tty(&io::stdout()) { + for file in &files { + writeln!(writer, "{}", file.name)?; + } + + return Ok(()); + } else if flags.order_left_to_right { + return print_grid(files, writer, Direction::LeftToRight); + } + + for file in &files { + let file_name = &file.name; if flags.comma_separate { write!(writer, "{}, ", file_name)?; @@ -195,6 +208,35 @@ fn print_default(files: Vec, writer: &mut W, flags: Flags) -> io Ok(()) } +fn print_grid(files: Vec, writer: &mut W, direction: Direction) -> io::Result<()> { + let io_error = |kind: io::ErrorKind, msg: &str| io::Error::new(kind, msg); + + let mut grid = Grid::new(GridOptions { filling: Filling::Spaces(2), direction }); + + let width = match tty_dimensions(&io::stdout()) { + Some(result) => result.0, + None => { + return Err(io_error(io::ErrorKind::Other, "Unable to retrieve terminal dimensions.")); + }, + }; + + for file in files { + grid.add(Cell { + alignment: Alignment::Left, + contents: file.file_name(), + width: file.name.len(), + }); + } + + match grid.fit_into_width(width.into()) { + Some(display) => { + write!(writer, "{}", display)?; + Ok(()) + }, + None => Err(io_error(io::ErrorKind::Other, "Cell width exceeds terminal width.")), + } +} + /// Prints information about the provided file in the long (`-l`) format fn print_list(files: Vec, writer: &mut W, flags: Flags) -> io::Result<()> { let mut inode_width = 1; From 70896bd45784f5049d1313916a002ae6637d20e1 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Fri, 6 Nov 2020 12:14:58 -0600 Subject: [PATCH 093/133] Ls: implement -C flag --- ls/src/cli.rs | 6 ++++++ ls/src/flags.rs | 5 ++++- ls/src/main.rs | 30 +++++++++++++++--------------- 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/ls/src/cli.rs b/ls/src/cli.rs index 2e19d366..a807ca67 100644 --- a/ls/src/cli.rs +++ b/ls/src/cli.rs @@ -45,6 +45,12 @@ pub(crate) fn create_app<'a, 'b>() -> App<'a, 'b> { .short("c") .long("file_status_modification"), ) + .arg( + Arg::with_name("order_top_to_bottom") + .help("Write multi-text-column output with entries sorted down the columns.") + .short("C") + .long("order_top_to_bottom"), + ) .arg( Arg::with_name("no_sort") .help( diff --git a/ls/src/flags.rs b/ls/src/flags.rs index 8da04bc9..2ebe00c4 100644 --- a/ls/src/flags.rs +++ b/ls/src/flags.rs @@ -18,6 +18,7 @@ pub(crate) struct Flags { pub no_sort: bool, pub numeric_uid_gid: bool, pub order_left_to_right: bool, + pub order_top_to_bottom: bool, pub reverse: bool, pub size: bool, pub sort_size: bool, @@ -42,6 +43,7 @@ impl Flags { let no_sort = matches.is_present("no_sort"); let numeric_uid_gid = matches.is_present("numeric_uid_gid"); let order_left_to_right = matches.is_present("order_left_to_right"); + let order_top_to_bottom = matches.is_present("order_top_to_bottom"); let reverse = matches.is_present("reverse"); let size = matches.is_present("size"); let sort_size = matches.is_present("sort_size"); @@ -63,6 +65,7 @@ impl Flags { no_sort, numeric_uid_gid, order_left_to_right, + order_top_to_bottom, reverse, size, sort_size, @@ -72,7 +75,7 @@ impl Flags { /// Whether to print as a list based ont the provided flags pub fn show_list(&self) -> bool { - !(self.comma_separate || self.order_left_to_right) + !(self.comma_separate || self.order_left_to_right || self.order_top_to_bottom) && (self.list || self.no_owner || self.numeric_uid_gid) } diff --git a/ls/src/main.rs b/ls/src/main.rs index 4ada1f15..03f45154 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -7,6 +7,7 @@ use std::{ }; use coreutils_core::os::tty::{is_tty, tty_dimensions}; + use term_grid::{Alignment, Cell, Direction, Filling, Grid, GridOptions}; extern crate chrono; @@ -188,24 +189,23 @@ fn print_default(files: Vec, writer: &mut W, flags: Flags) -> io } return Ok(()); - } else if flags.order_left_to_right { - return print_grid(files, writer, Direction::LeftToRight); - } - - for file in &files { - let file_name = &file.name; - - if flags.comma_separate { - write!(writer, "{}, ", file_name)?; - } else { - writeln!(writer, "{}", file_name)?; + } else if flags.comma_separate { + for (i, file) in files.iter().enumerate() { + let file_name = &file.name; + + if (i + 1) == files.len() { + writeln!(writer, "{}", file_name)?; + } else { + write!(writer, "{}, ", file_name)?; + } } - } - if flags.comma_separate { - writeln!(writer)?; + + return Ok(()); + } else if flags.order_left_to_right && !flags.order_top_to_bottom { + return print_grid(files, writer, Direction::LeftToRight); } - Ok(()) + print_grid(files, writer, Direction::TopToBottom) } fn print_grid(files: Vec, writer: &mut W, direction: Direction) -> io::Result<()> { From 3451df463c57ed234b82b9784299828e65e2ef78 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Fri, 6 Nov 2020 12:19:38 -0600 Subject: [PATCH 094/133] Ls: run cargo fmt --- coreutils_core/src/os/tty.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/coreutils_core/src/os/tty.rs b/coreutils_core/src/os/tty.rs index 19888e45..6dd692ef 100644 --- a/coreutils_core/src/os/tty.rs +++ b/coreutils_core/src/os/tty.rs @@ -113,12 +113,7 @@ pub fn tty_dimensions(file_descriptor: &impl AsRawFd) -> Option<(u16, u16)> { return None; } - let mut size = winsize { - ws_row: 0, - ws_col: 0, - ws_xpixel: 0, - ws_ypixel: 0, - }; + let mut size = winsize { ws_row: 0, ws_col: 0, ws_xpixel: 0, ws_ypixel: 0 }; if unsafe { ioctl(file_descriptor.as_raw_fd(), TIOCGWINSZ, &mut size) } == -1 { return None; From 2b09f068201547e355e1654d21ea8c2f20638290 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Fri, 6 Nov 2020 12:23:16 -0600 Subject: [PATCH 095/133] Ls: update example --- coreutils_core/src/os/tty.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coreutils_core/src/os/tty.rs b/coreutils_core/src/os/tty.rs index 6dd692ef..801273c0 100644 --- a/coreutils_core/src/os/tty.rs +++ b/coreutils_core/src/os/tty.rs @@ -105,7 +105,7 @@ pub fn is_tty(file_descriptor: &impl AsRawFd) -> bool { /// ## Example /// ``` rust /// use coreutils_core::os::tty::tty_dimensions; -/// let (width, height) = tty_dimensions(&std::io::stdout()).unwrap(); +/// let dimensions = tty_dimensions(&std::io::stdout()); /// ``` #[inline] pub fn tty_dimensions(file_descriptor: &impl AsRawFd) -> Option<(u16, u16)> { From 2adb5200c4d4af6b92dae6a39f1f496c5bd05ee7 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Fri, 6 Nov 2020 12:47:16 -0600 Subject: [PATCH 096/133] Ls: attempt to fix error in freebsd --- coreutils_core/src/os/tty.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/coreutils_core/src/os/tty.rs b/coreutils_core/src/os/tty.rs index 801273c0..9d59ec4c 100644 --- a/coreutils_core/src/os/tty.rs +++ b/coreutils_core/src/os/tty.rs @@ -115,6 +115,9 @@ pub fn tty_dimensions(file_descriptor: &impl AsRawFd) -> Option<(u16, u16)> { let mut size = winsize { ws_row: 0, ws_col: 0, ws_xpixel: 0, ws_ypixel: 0 }; + #[cfg(target_os = "freebsd")] + let TIOCGWINSZ: u64 = TIOCGWINSZ.into(); + if unsafe { ioctl(file_descriptor.as_raw_fd(), TIOCGWINSZ, &mut size) } == -1 { return None; } From 7a8be6512ebaf2a99c9d9a32a4f4a651b165db92 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Fri, 6 Nov 2020 14:19:10 -0600 Subject: [PATCH 097/133] Ls: attempt to fix platform specific error --- coreutils_core/src/os/tty.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/coreutils_core/src/os/tty.rs b/coreutils_core/src/os/tty.rs index 9d59ec4c..7ed50d3d 100644 --- a/coreutils_core/src/os/tty.rs +++ b/coreutils_core/src/os/tty.rs @@ -115,10 +115,12 @@ pub fn tty_dimensions(file_descriptor: &impl AsRawFd) -> Option<(u16, u16)> { let mut size = winsize { ws_row: 0, ws_col: 0, ws_xpixel: 0, ws_ypixel: 0 }; + let tiocgwinsz = TIOCGWINSZ; + #[cfg(target_os = "freebsd")] - let TIOCGWINSZ: u64 = TIOCGWINSZ.into(); + let tiocgwinsz: u64 = tiocgwinsz.into(); - if unsafe { ioctl(file_descriptor.as_raw_fd(), TIOCGWINSZ, &mut size) } == -1 { + if unsafe { ioctl(file_descriptor.as_raw_fd(), tiocgwinsz, &mut size) } == -1 { return None; } From 84c26af7ce2bc16f37586edf1aae7efbf3d93d26 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Fri, 6 Nov 2020 14:19:24 -0600 Subject: [PATCH 098/133] Ls: default to newlines if unable to use grid --- ls/src/main.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ls/src/main.rs b/ls/src/main.rs index 03f45154..f6aea817 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -220,7 +220,7 @@ fn print_grid(files: Vec, writer: &mut W, direction: Direction) }, }; - for file in files { + for file in &files { grid.add(Cell { alignment: Alignment::Left, contents: file.file_name(), @@ -233,7 +233,11 @@ fn print_grid(files: Vec, writer: &mut W, direction: Direction) write!(writer, "{}", display)?; Ok(()) }, - None => Err(io_error(io::ErrorKind::Other, "Cell width exceeds terminal width.")), + None => { + Ok(for file in &files { + writeln!(writer, "{}", file.file_name())?; + }) + }, } } From 212f6356394115aedce26b088fc98a4e485a3940 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Fri, 6 Nov 2020 14:34:38 -0600 Subject: [PATCH 099/133] Ls: add char device color --- ls/src/file.rs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/ls/src/file.rs b/ls/src/file.rs index 8fce75f8..db6ee660 100644 --- a/ls/src/file.rs +++ b/ls/src/file.rs @@ -194,6 +194,8 @@ impl File { pub fn file_name(&self) -> String { let mut file_name = self.name.clone(); + let file_type = self.metadata.file_type(); + let flags = self.flags; if File::is_executable(&self.path) { @@ -204,7 +206,7 @@ impl File { } } - if self.metadata.file_type().is_symlink() && !flags.dereference { + if file_type.is_symlink() && !flags.dereference { file_name = self.add_symlink_color(file_name); if flags.classify && !flags.show_list() { @@ -230,14 +232,18 @@ impl File { } } - if self.metadata.file_type().is_fifo() { - file_name = self.add_named_pipe_color(file_name); + if file_type.is_fifo() { + file_name = self.add_fifo_color(file_name); if flags.classify { file_name = format!("{}|", file_name); } } + if file_type.is_char_device() { + file_name = self.add_char_device_color(file_name); + } + if self.metadata.is_dir() { file_name = self.add_directory_color(file_name); @@ -259,10 +265,14 @@ impl File { Color::Blue.bold().paint(directory_name).to_string() } - pub fn add_named_pipe_color(&self, named_pipe_name: String) -> String { + pub fn add_fifo_color(&self, named_pipe_name: String) -> String { Color::Yellow.on(Color::Black).paint(named_pipe_name).to_string() } + pub fn add_char_device_color(&self, char_device_name: String) -> String { + Color::Yellow.on(Color::Black).bold().paint(char_device_name).to_string() + } + /// Adds a bold cyan color to a file name to represent a symlink pub fn add_symlink_color(&self, symlink_name: String) -> String { Color::Cyan.bold().paint(symlink_name).to_string() From 6e74c47cb818c0e3763f1d338c8d2afde6213b48 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Fri, 6 Nov 2020 14:34:55 -0600 Subject: [PATCH 100/133] Ls: format code --- ls/src/main.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/ls/src/main.rs b/ls/src/main.rs index f6aea817..cc3d372e 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -233,11 +233,9 @@ fn print_grid(files: Vec, writer: &mut W, direction: Direction) write!(writer, "{}", display)?; Ok(()) }, - None => { - Ok(for file in &files { - writeln!(writer, "{}", file.file_name())?; - }) - }, + None => Ok(for file in &files { + writeln!(writer, "{}", file.file_name())?; + }), } } From 7c22ad03a9e6e5826fc5d2036012f2a0cae54c75 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Fri, 6 Nov 2020 16:15:04 -0600 Subject: [PATCH 101/133] Ls: optionally show file color --- ls/src/file.rs | 26 ++++++++++++++++++++------ ls/src/main.rs | 18 +++++++++++------- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/ls/src/file.rs b/ls/src/file.rs index db6ee660..4f74da57 100644 --- a/ls/src/file.rs +++ b/ls/src/file.rs @@ -19,6 +19,12 @@ use chrono::{DateTime, Local, TimeZone}; use crate::flags::Flags; +#[derive(PartialEq, Eq)] +pub(crate) enum FileColor { + Show, + Hide, +} + /// Represents a file and it's properties pub(crate) struct File { pub name: String, @@ -191,7 +197,7 @@ impl File { } /// Gets a file name from a directory entry and adds appropriate formatting - pub fn file_name(&self) -> String { + pub fn file_name(&self, color: FileColor) -> String { let mut file_name = self.name.clone(); let file_type = self.metadata.file_type(); @@ -199,7 +205,9 @@ impl File { let flags = self.flags; if File::is_executable(&self.path) { - file_name = self.add_executable_color(file_name); + if color == FileColor::Show { + file_name = self.add_executable_color(file_name); + } if flags.classify { file_name = format!("{}*", file_name); @@ -207,7 +215,9 @@ impl File { } if file_type.is_symlink() && !flags.dereference { - file_name = self.add_symlink_color(file_name); + if color == FileColor::Show { + file_name = self.add_symlink_color(file_name); + } if flags.classify && !flags.show_list() { file_name = format!("{}@", file_name); @@ -233,19 +243,23 @@ impl File { } if file_type.is_fifo() { - file_name = self.add_fifo_color(file_name); + if color == FileColor::Show { + file_name = self.add_fifo_color(file_name); + } if flags.classify { file_name = format!("{}|", file_name); } } - if file_type.is_char_device() { + if file_type.is_char_device() && color == FileColor::Show { file_name = self.add_char_device_color(file_name); } if self.metadata.is_dir() { - file_name = self.add_directory_color(file_name); + if color == FileColor::Show { + file_name = self.add_directory_color(file_name); + } if flags.classify || flags.indicator { file_name = format!("{}/", file_name); diff --git a/ls/src/main.rs b/ls/src/main.rs index cc3d372e..503889c5 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -17,7 +17,7 @@ mod file; mod flags; mod table; -use file::File; +use file::{File, FileColor}; use flags::Flags; use table::{Row, Table}; @@ -223,8 +223,8 @@ fn print_grid(files: Vec, writer: &mut W, direction: Direction) for file in &files { grid.add(Cell { alignment: Alignment::Left, - contents: file.file_name(), - width: file.name.len(), + contents: file.file_name(FileColor::Show), + width: file.file_name(FileColor::Hide).len(), }); } @@ -233,9 +233,13 @@ fn print_grid(files: Vec, writer: &mut W, direction: Direction) write!(writer, "{}", display)?; Ok(()) }, - None => Ok(for file in &files { - writeln!(writer, "{}", file.file_name())?; - }), + None => { + for file in &files { + writeln!(writer, "{}", file.file_name(FileColor::Show))?; + } + + Ok(()) + }, } } @@ -349,7 +353,7 @@ fn print_list(files: Vec, writer: &mut W, flags: Flags) -> io::R row.time = file.time(); // Process the file's name - row.file_name = file.file_name(); + row.file_name = file.file_name(FileColor::Show); rows.push(row); } From 929a632b9aa943ed471abb1ff47e7db92b63b747 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Mon, 9 Nov 2020 09:58:13 -0600 Subject: [PATCH 102/133] Ls: switch to type alias --- ls/src/main.rs | 2 +- ls/src/table.rs | 16 +--------------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/ls/src/main.rs b/ls/src/main.rs index 503889c5..1a5885b4 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -360,7 +360,7 @@ fn print_list(files: Vec, writer: &mut W, flags: Flags) -> io::R writeln!(writer, "total {}", total)?; - for row in rows.rows { + for row in rows { if flags.inode { write!(writer, "{:>1$} ", row.inode, inode_width)?; } diff --git a/ls/src/table.rs b/ls/src/table.rs index 9254110d..2fec974a 100644 --- a/ls/src/table.rs +++ b/ls/src/table.rs @@ -1,19 +1,5 @@ /// Contains each row displayed in the -l` option. -pub(crate) struct Table { - pub rows: Vec, -} - -impl Table { - /// Initialize a new table - pub fn new() -> Self { - let rows = Vec::new(); - - Table { rows } - } - - /// Add a row the list of table. - pub fn push(&mut self, row: Row) { self.rows.push(row) } -} +pub(crate) type Table = Vec; /// Contains each column displayed in the `-l` option. pub(crate) struct Row { From 8fb46dd79953525a82286a778964b19c52e5585b Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Mon, 9 Nov 2020 12:00:24 -0600 Subject: [PATCH 103/133] Ls: implement -d flag --- ls/src/cli.rs | 6 ++++++ ls/src/flags.rs | 3 +++ ls/src/main.rs | 10 +++++++--- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/ls/src/cli.rs b/ls/src/cli.rs index a807ca67..bb694761 100644 --- a/ls/src/cli.rs +++ b/ls/src/cli.rs @@ -51,6 +51,12 @@ pub(crate) fn create_app<'a, 'b>() -> App<'a, 'b> { .short("C") .long("order_top_to_bottom"), ) + .arg( + Arg::with_name("directory") + .help("List directories and files themselves, rather than their contents.") + .short("d") + .long("directory"), + ) .arg( Arg::with_name("no_sort") .help( diff --git a/ls/src/flags.rs b/ls/src/flags.rs index 2ebe00c4..11d6b0b8 100644 --- a/ls/src/flags.rs +++ b/ls/src/flags.rs @@ -8,6 +8,7 @@ pub(crate) struct Flags { pub block_size: bool, pub classify: bool, pub comma_separate: bool, + pub directory: bool, pub dereference: bool, pub file_status_modification: bool, pub indicator: bool, @@ -34,6 +35,7 @@ impl Flags { let classify = matches.is_present("classify"); let comma_separate = matches.is_present("comma_separate"); let dereference = matches.is_present("dereference"); + let directory = matches.is_present("directory"); let file_status_modification = matches.is_present("file_status_modification"); let indicator = matches.is_present("indicator"); let inode = matches.is_present("inode"); @@ -55,6 +57,7 @@ impl Flags { block_size, classify, comma_separate, + directory, dereference, file_status_modification, inode, diff --git a/ls/src/main.rs b/ls/src/main.rs index 1a5885b4..9c354630 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -58,10 +58,14 @@ fn main() { let path = path::PathBuf::from(file); - if path.is_file() { + if flags.directory || path.is_file() { result = Vec::new(); - let item = File::from(path::PathBuf::from(file), flags); + let item = if flags.directory { + File::from_name(path.to_string_lossy().to_string(), path, flags) + } else { + File::from(path::PathBuf::from(file), flags) + }; match item { Ok(item) => { @@ -115,7 +119,7 @@ fn main() { } } - if flags.all || flags.no_sort { + if !flags.directory && (flags.all || flags.no_sort) { // Retrieve the current directories information. This must // be canonicalize incase the path is relative let current = path::PathBuf::from(file).canonicalize(); From 69f71e621114a3b62cac68d17306b5817549656b Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Tue, 10 Nov 2020 11:07:29 -0600 Subject: [PATCH 104/133] Ls: implement -o flag --- ls/src/cli.rs | 6 ++++++ ls/src/flags.rs | 5 ++++- ls/src/main.rs | 38 +++++++++++++++++++++----------------- 3 files changed, 31 insertions(+), 18 deletions(-) diff --git a/ls/src/cli.rs b/ls/src/cli.rs index bb694761..a959f612 100644 --- a/ls/src/cli.rs +++ b/ls/src/cli.rs @@ -130,6 +130,12 @@ pub(crate) fn create_app<'a, 'b>() -> App<'a, 'b> { .short("n") .long("numeric-uid-gid"), ) + .arg( + Arg::with_name("no_group") + .help("Like -l, but do not list group.") + .short("o") + .long("no-group"), + ) .arg( Arg::with_name("reverse") .help("Reverse order while sorting.") diff --git a/ls/src/flags.rs b/ls/src/flags.rs index 11d6b0b8..438d7a2c 100644 --- a/ls/src/flags.rs +++ b/ls/src/flags.rs @@ -15,6 +15,7 @@ pub(crate) struct Flags { pub inode: bool, pub last_accessed: bool, pub list: bool, + pub no_group: bool, pub no_owner: bool, pub no_sort: bool, pub numeric_uid_gid: bool, @@ -41,6 +42,7 @@ impl Flags { let inode = matches.is_present("inode"); let last_accessed = matches.is_present("last_accessed"); let list = matches.is_present("list"); + let no_group = matches.is_present("no_group"); let no_owner = matches.is_present("no_owner"); let no_sort = matches.is_present("no_sort"); let numeric_uid_gid = matches.is_present("numeric_uid_gid"); @@ -64,6 +66,7 @@ impl Flags { indicator, last_accessed, list, + no_group, no_owner, no_sort, numeric_uid_gid, @@ -79,7 +82,7 @@ impl Flags { /// Whether to print as a list based ont the provided flags pub fn show_list(&self) -> bool { !(self.comma_separate || self.order_left_to_right || self.order_top_to_bottom) - && (self.list || self.no_owner || self.numeric_uid_gid) + && (self.list || self.no_owner || self.no_group || self.numeric_uid_gid) } /// Whether or not to show hidden files and directories diff --git a/ls/src/main.rs b/ls/src/main.rs index 9c354630..dbd81416 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -308,24 +308,26 @@ fn print_list(files: Vec, writer: &mut W, flags: Flags) -> io::R row.hard_links = hard_links; // Process the file's user name - let user = match file.user() { - Ok(file_user) => file_user, - Err(err) => { - eprintln!("ls: {}", err); - file.metadata.uid().to_string() - }, - }; - - let user_len = user.len(); - - if user_len > user_width { - user_width = user_len; - } + if !flags.no_owner { + let user = match file.user() { + Ok(file_user) => file_user, + Err(err) => { + eprintln!("ls: {}", err); + file.metadata.uid().to_string() + }, + }; + + let user_len = user.len(); - row.user = user; + if user_len > user_width { + user_width = user_len; + } + + row.user = user; + } // Process the file's group name - if !flags.no_owner { + if !flags.no_group { let group = match file.group() { Ok(file_group) => file_group, Err(err) => { @@ -377,9 +379,11 @@ fn print_list(files: Vec, writer: &mut W, flags: Flags) -> io::R write!(writer, "{:>1$} ", row.hard_links, hard_links_width)?; - write!(writer, "{:<1$} ", row.user, user_width)?; - if !flags.no_owner { + write!(writer, "{:<1$} ", row.user, user_width)?; + } + + if !flags.no_group { write!(writer, "{:<1$} ", row.group, group_width)?; } From 077f1a832ba8d91ea127debe7a929e02e1f3d04e Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Tue, 10 Nov 2020 11:19:33 -0600 Subject: [PATCH 105/133] Ls: only colorize file names if output is a tty --- ls/src/file.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/ls/src/file.rs b/ls/src/file.rs index 4f74da57..0058e6a0 100644 --- a/ls/src/file.rs +++ b/ls/src/file.rs @@ -1,6 +1,7 @@ use coreutils_core::os::{ group::{Error as GroupError, Group}, passwd::{Error as PasswdError, Passwd}, + tty::is_tty, }; use std::{ @@ -198,6 +199,9 @@ impl File { /// Gets a file name from a directory entry and adds appropriate formatting pub fn file_name(&self, color: FileColor) -> String { + // Determine if the file name should have a color applied. + let show_color = color == FileColor::Show && is_tty(&io::stdout()); + let mut file_name = self.name.clone(); let file_type = self.metadata.file_type(); @@ -205,7 +209,7 @@ impl File { let flags = self.flags; if File::is_executable(&self.path) { - if color == FileColor::Show { + if show_color { file_name = self.add_executable_color(file_name); } @@ -215,7 +219,7 @@ impl File { } if file_type.is_symlink() && !flags.dereference { - if color == FileColor::Show { + if show_color { file_name = self.add_symlink_color(file_name); } @@ -243,7 +247,7 @@ impl File { } if file_type.is_fifo() { - if color == FileColor::Show { + if show_color { file_name = self.add_fifo_color(file_name); } @@ -252,12 +256,12 @@ impl File { } } - if file_type.is_char_device() && color == FileColor::Show { + if file_type.is_char_device() && show_color { file_name = self.add_char_device_color(file_name); } if self.metadata.is_dir() { - if color == FileColor::Show { + if show_color { file_name = self.add_directory_color(file_name); } From f640f2dc6adc418a3e7e8758633ad510bc563b01 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Thu, 12 Nov 2020 14:30:00 -0600 Subject: [PATCH 106/133] Ls: use bstr to deal with non utf-8 chars --- ls/src/file.rs | 99 ++++++++++++++++++++++++------------------------- ls/src/main.rs | 22 ++++++----- ls/src/table.rs | 6 ++- 3 files changed, 65 insertions(+), 62 deletions(-) diff --git a/ls/src/file.rs b/ls/src/file.rs index 0058e6a0..b80909a1 100644 --- a/ls/src/file.rs +++ b/ls/src/file.rs @@ -1,12 +1,18 @@ -use coreutils_core::os::{ - group::{Error as GroupError, Group}, - passwd::{Error as PasswdError, Passwd}, - tty::is_tty, +use coreutils_core::{ + bstr::{BStr, BString}, + os::{ + group::{Error as GroupError, Group}, + passwd::{Error as PasswdError, Passwd}, + tty::is_tty, + }, }; use std::{ fs, io, - os::unix::fs::{FileTypeExt, MetadataExt, PermissionsExt}, + os::unix::{ + ffi::OsStrExt, + fs::{FileTypeExt, MetadataExt, PermissionsExt}, + }, path, result::Result, string::String, @@ -28,7 +34,7 @@ pub(crate) enum FileColor { /// Represents a file and it's properties pub(crate) struct File { - pub name: String, + pub name: BString, pub path: path::PathBuf, pub metadata: fs::Metadata, flags: Flags, @@ -42,20 +48,20 @@ impl File { if flags.dereference && metadata.file_type().is_symlink() { let symlink = fs::read_link(path.clone())?; - let name = File::path_buf_to_file_name(&symlink)?.to_string(); + let name = File::path_buf_to_file_name(&symlink)?; let metadata = path.metadata()?; return Ok(File { name, path: symlink, metadata, flags }); } - let name = File::path_buf_to_file_name(&path)?.to_string(); + let name = File::path_buf_to_file_name(&path)?; Ok(File { name, path, metadata, flags }) } /// Creates a `File` instance from a `DirEntry` and supplies a file name - pub fn from_name(name: String, path: path::PathBuf, flags: Flags) -> io::Result { + pub fn from_name(name: BString, path: path::PathBuf, flags: Flags) -> io::Result { let metadata = path.metadata()?; Ok(File { name, path, metadata, flags }) @@ -102,13 +108,13 @@ impl File { /// Retrieves the file's group name as a string. If the `-n` flag is set, /// the the group's ID is returned - pub fn group(&self) -> Result { + pub fn group(&self) -> Result { if self.flags.numeric_uid_gid { - return Ok(self.metadata.gid().to_string()); + return Ok(BString::from(self.metadata.gid().to_string())); } match Group::from_gid(self.metadata.gid()) { - Ok(group) => Ok(group.name().to_string()), + Ok(group) => Ok(BString::from(group.name())), Err(err) => Err(err), } } @@ -167,13 +173,13 @@ impl File { } /// Checks if a string looks like a hidden unix file name - pub fn is_hidden(name: &str) -> bool { name.starts_with('.') } + pub fn is_hidden(name: &BStr) -> bool { name.to_string().starts_with('.') } /// Gets the file name from a `PathBuf` /// /// Will return `Error` if the path terminates at '..' or if the file name /// contains invalid unicode characters. - pub fn path_buf_to_file_name(path: &path::PathBuf) -> io::Result<&str> { + pub fn path_buf_to_file_name(path: &path::PathBuf) -> io::Result { // Create a new IO Error. let io_error = |kind: io::ErrorKind, msg: &str| io::Error::new(kind, msg); @@ -184,17 +190,7 @@ impl File { }, }; - let file_name = match file_name.to_str() { - Some(file_name) => file_name, - None => { - return Err(io_error( - io::ErrorKind::InvalidData, - "File name contains invalid unicode", - )); - }, - }; - - Ok(file_name) + Ok(BString::from(file_name.as_bytes())) } /// Gets a file name from a directory entry and adds appropriate formatting @@ -202,7 +198,9 @@ impl File { // Determine if the file name should have a color applied. let show_color = color == FileColor::Show && is_tty(&io::stdout()); - let mut file_name = self.name.clone(); + let file_name = self.name.clone(); + + let mut result = file_name.to_string(); let file_type = self.metadata.file_type(); @@ -210,89 +208,90 @@ impl File { if File::is_executable(&self.path) { if show_color { - file_name = self.add_executable_color(file_name); + result = self.add_executable_color(&file_name); } if flags.classify { - file_name = format!("{}*", file_name); + result = format!("{}*", result); } } if file_type.is_symlink() && !flags.dereference { if show_color { - file_name = self.add_symlink_color(file_name); + result = self.add_symlink_color(&file_name); } if flags.classify && !flags.show_list() { - file_name = format!("{}@", file_name); + result = format!("{}@", result); } if flags.show_list() { let symlink = fs::read_link(self.path.clone()); if let Ok(symlink) = symlink { - let mut symlink_name = symlink.to_string_lossy().to_string(); + let symlink_name = BString::from(symlink.as_os_str().as_bytes()); + let mut symlink_result = symlink_name.to_string(); if File::is_executable(&symlink) { - symlink_name = self.add_executable_color(symlink_name); + symlink_result = self.add_executable_color(&symlink_name); if flags.classify { - symlink_name = format!("{}*", symlink_name); + symlink_result = format!("{}*", symlink_result); } } - file_name = format!("{} -> {}", file_name, symlink_name); + result = format!("{} -> {}", result, symlink_result); } } } if file_type.is_fifo() { if show_color { - file_name = self.add_fifo_color(file_name); + result = self.add_fifo_color(&file_name); } if flags.classify { - file_name = format!("{}|", file_name); + result = format!("{}|", result); } } if file_type.is_char_device() && show_color { - file_name = self.add_char_device_color(file_name); + result = self.add_char_device_color(&file_name); } if self.metadata.is_dir() { if show_color { - file_name = self.add_directory_color(file_name); + result = self.add_directory_color(&file_name); } if flags.classify || flags.indicator { - file_name = format!("{}/", file_name); + result = format!("{}/", result); } } - file_name + result } /// Adds a bold green color to a file name to represent an executable - pub fn add_executable_color(&self, file_name: String) -> String { - Color::Green.bold().paint(file_name).to_string() + pub fn add_executable_color(&self, file_name: &BString) -> String { + Color::Green.bold().paint(file_name.to_string()).to_string() } /// Adds a bold blue color to a directory name - pub fn add_directory_color(&self, directory_name: String) -> String { - Color::Blue.bold().paint(directory_name).to_string() + pub fn add_directory_color(&self, directory_name: &BString) -> String { + Color::Blue.bold().paint(directory_name.to_string()).to_string() } - pub fn add_fifo_color(&self, named_pipe_name: String) -> String { - Color::Yellow.on(Color::Black).paint(named_pipe_name).to_string() + pub fn add_fifo_color(&self, named_pipe_name: &BString) -> String { + Color::Yellow.on(Color::Black).paint(named_pipe_name.to_string()).to_string() } - pub fn add_char_device_color(&self, char_device_name: String) -> String { - Color::Yellow.on(Color::Black).bold().paint(char_device_name).to_string() + pub fn add_char_device_color(&self, char_device_name: &BString) -> String { + Color::Yellow.on(Color::Black).bold().paint(char_device_name.to_string()).to_string() } /// Adds a bold cyan color to a file name to represent a symlink - pub fn add_symlink_color(&self, symlink_name: String) -> String { - Color::Cyan.bold().paint(symlink_name).to_string() + pub fn add_symlink_color(&self, symlink_name: &BString) -> String { + Color::Cyan.bold().paint(symlink_name.to_string()).to_string() } } diff --git a/ls/src/main.rs b/ls/src/main.rs index dbd81416..7213b585 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -1,7 +1,11 @@ +use coreutils_core::{BString, ByteSlice}; use std::{ fs, io::{self, BufWriter, Write}, - os::unix::fs::MetadataExt, + os::unix::{ + ffi::OsStrExt, + fs::MetadataExt, + }, path, process, string::String, }; @@ -62,7 +66,7 @@ fn main() { result = Vec::new(); let item = if flags.directory { - File::from_name(path.to_string_lossy().to_string(), path, flags) + File::from_name(BString::from(path.as_os_str().as_bytes()), path, flags) } else { File::from(path::PathBuf::from(file), flags) }; @@ -84,7 +88,7 @@ fn main() { .map(|entry| File::from(entry.unwrap().path(), flags).unwrap()) // Hide hidden files and directories if `-a` or `-A` flags // weren't provided - .filter(|file| !File::is_hidden(&file.name) || flags.show_hidden()) + .filter(|file| !File::is_hidden(&file.name.as_bstr()) || flags.show_hidden()) .collect(); if !flags.no_sort { @@ -132,7 +136,7 @@ fn main() { }, }; - let dot = File::from_name(".".to_string(), current.clone(), flags); + let dot = File::from_name(BString::from("."), current.clone(), flags); let dot = match dot { Ok(dot) => dot, @@ -147,7 +151,7 @@ fn main() { let parent_path = path::PathBuf::from(dot.path.parent().unwrap_or_else(|| current.as_path())); - let dot_dot = File::from_name("..".to_string(), parent_path, flags); + let dot_dot = File::from_name(BString::from(".."), parent_path, flags); let dot_dot = match dot_dot { Ok(dot_dot) => dot_dot, @@ -213,14 +217,12 @@ fn print_default(files: Vec, writer: &mut W, flags: Flags) -> io } fn print_grid(files: Vec, writer: &mut W, direction: Direction) -> io::Result<()> { - let io_error = |kind: io::ErrorKind, msg: &str| io::Error::new(kind, msg); - let mut grid = Grid::new(GridOptions { filling: Filling::Spaces(2), direction }); let width = match tty_dimensions(&io::stdout()) { Some(result) => result.0, None => { - return Err(io_error(io::ErrorKind::Other, "Unable to retrieve terminal dimensions.")); + return Err(io::Error::new(io::ErrorKind::Other, "Unable to retrieve terminal dimensions.")); }, }; @@ -332,7 +334,7 @@ fn print_list(files: Vec, writer: &mut W, flags: Flags) -> io::R Ok(file_group) => file_group, Err(err) => { eprintln!("ls: {}", err); - file.metadata.gid().to_string() + BString::from(file.metadata.gid().to_string()) }, }; @@ -407,7 +409,7 @@ fn sort_by_ctime(file: &File) -> i64 { file.metadata.ctime() } /// Sort a list of files by file name alphabetically fn sort_by_name(file: &File) -> String { - file.name.to_lowercase().trim_start_matches('.').to_string() + file.name.to_string().to_lowercase().trim_start_matches('.').to_string() } /// Sort a list of files by size diff --git a/ls/src/table.rs b/ls/src/table.rs index 2fec974a..0ecf054d 100644 --- a/ls/src/table.rs +++ b/ls/src/table.rs @@ -1,3 +1,5 @@ +use coreutils_core::BString; + /// Contains each row displayed in the -l` option. pub(crate) type Table = Vec; @@ -8,7 +10,7 @@ pub(crate) struct Row { pub permissions: String, pub hard_links: String, pub user: String, - pub group: String, + pub group: BString, pub size: String, pub time: String, pub file_name: String, @@ -22,7 +24,7 @@ impl Row { let permissions = String::new(); let hard_links = String::new(); let user = String::new(); - let group = String::new(); + let group = BString::from(""); let size = String::new(); let time = String::new(); let file_name = String::new(); From 470df1f33a619b0576911d50ed5d185b8e5292b2 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Thu, 12 Nov 2020 14:30:36 -0600 Subject: [PATCH 107/133] Ls: run cargo fmt --- ls/src/main.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ls/src/main.rs b/ls/src/main.rs index 7213b585..c5e11986 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -2,10 +2,7 @@ use coreutils_core::{BString, ByteSlice}; use std::{ fs, io::{self, BufWriter, Write}, - os::unix::{ - ffi::OsStrExt, - fs::MetadataExt, - }, + os::unix::{ffi::OsStrExt, fs::MetadataExt}, path, process, string::String, }; @@ -222,7 +219,10 @@ fn print_grid(files: Vec, writer: &mut W, direction: Direction) let width = match tty_dimensions(&io::stdout()) { Some(result) => result.0, None => { - return Err(io::Error::new(io::ErrorKind::Other, "Unable to retrieve terminal dimensions.")); + return Err(io::Error::new( + io::ErrorKind::Other, + "Unable to retrieve terminal dimensions.", + )); }, }; From 8f44561fd7ccae5a79c1497887534daab67a7f9b Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Mon, 16 Nov 2020 10:37:40 -0600 Subject: [PATCH 108/133] Ls: use bstring rather than string --- ls/src/file.rs | 10 +++++----- ls/src/main.rs | 12 +++++++----- ls/src/table.rs | 4 ++-- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/ls/src/file.rs b/ls/src/file.rs index b80909a1..496edbf5 100644 --- a/ls/src/file.rs +++ b/ls/src/file.rs @@ -1,5 +1,5 @@ use coreutils_core::{ - bstr::{BStr, BString}, + BStr, BString, os::{ group::{Error as GroupError, Group}, passwd::{Error as PasswdError, Passwd}, @@ -95,13 +95,13 @@ impl File { /// Retrieves the file's user name as a string. If the `-n` flag is set, /// the the user's ID is returned - pub fn user(&self) -> Result { + pub fn user(&self) -> Result { if self.flags.numeric_uid_gid { - return Ok(self.metadata.uid().to_string()); + return Ok(BString::from(self.metadata.uid().to_string())); } match Passwd::from_uid(self.metadata.uid()) { - Ok(passwd) => Ok(passwd.name().to_string()), + Ok(passwd) => Ok(passwd.name().to_owned()), Err(err) => Err(err), } } @@ -114,7 +114,7 @@ impl File { } match Group::from_gid(self.metadata.gid()) { - Ok(group) => Ok(BString::from(group.name())), + Ok(group) => Ok(group.name().to_owned()), Err(err) => Err(err), } } diff --git a/ls/src/main.rs b/ls/src/main.rs index c5e11986..b4c22082 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -1,4 +1,3 @@ -use coreutils_core::{BString, ByteSlice}; use std::{ fs, io::{self, BufWriter, Write}, @@ -7,7 +6,10 @@ use std::{ string::String, }; -use coreutils_core::os::tty::{is_tty, tty_dimensions}; +use coreutils_core::{ + BString, ByteSlice, + os::tty::{is_tty, tty_dimensions}, +}; use term_grid::{Alignment, Cell, Direction, Filling, Grid, GridOptions}; @@ -315,7 +317,7 @@ fn print_list(files: Vec, writer: &mut W, flags: Flags) -> io::R Ok(file_user) => file_user, Err(err) => { eprintln!("ls: {}", err); - file.metadata.uid().to_string() + BString::from(file.metadata.uid().to_string()) }, }; @@ -382,11 +384,11 @@ fn print_list(files: Vec, writer: &mut W, flags: Flags) -> io::R write!(writer, "{:>1$} ", row.hard_links, hard_links_width)?; if !flags.no_owner { - write!(writer, "{:<1$} ", row.user, user_width)?; + write!(writer, "{:<1$} ", row.user.to_string(), user_width)?; } if !flags.no_group { - write!(writer, "{:<1$} ", row.group, group_width)?; + write!(writer, "{:<1$} ", row.group.to_string(), group_width)?; } write!(writer, "{:>1$} ", row.size, size_width)?; diff --git a/ls/src/table.rs b/ls/src/table.rs index 0ecf054d..1af206d5 100644 --- a/ls/src/table.rs +++ b/ls/src/table.rs @@ -9,7 +9,7 @@ pub(crate) struct Row { pub block: String, pub permissions: String, pub hard_links: String, - pub user: String, + pub user: BString, pub group: BString, pub size: String, pub time: String, @@ -23,7 +23,7 @@ impl Row { let block = String::new(); let permissions = String::new(); let hard_links = String::new(); - let user = String::new(); + let user = BString::from(""); let group = BString::from(""); let size = String::new(); let time = String::new(); From 7419ae4f427abb377929101747b3fb4a2927f4ae Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Mon, 16 Nov 2020 10:57:40 -0600 Subject: [PATCH 109/133] Ls: format code --- ls/src/file.rs | 2 +- ls/src/main.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ls/src/file.rs b/ls/src/file.rs index 496edbf5..92780d5a 100644 --- a/ls/src/file.rs +++ b/ls/src/file.rs @@ -1,10 +1,10 @@ use coreutils_core::{ - BStr, BString, os::{ group::{Error as GroupError, Group}, passwd::{Error as PasswdError, Passwd}, tty::is_tty, }, + BStr, BString, }; use std::{ diff --git a/ls/src/main.rs b/ls/src/main.rs index b4c22082..98083c5a 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -7,8 +7,8 @@ use std::{ }; use coreutils_core::{ - BString, ByteSlice, os::tty::{is_tty, tty_dimensions}, + BString, ByteSlice, }; use term_grid::{Alignment, Cell, Direction, Filling, Grid, GridOptions}; From 5f6e3112855fe976569d91dc2c3b5a91ff8952a9 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Mon, 16 Nov 2020 14:02:27 -0600 Subject: [PATCH 110/133] Ls: clean up --- ls/src/main.rs | 66 ++++++++++++++++++++++++++++---------------------- 1 file changed, 37 insertions(+), 29 deletions(-) diff --git a/ls/src/main.rs b/ls/src/main.rs index 98083c5a..b3c97462 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -2,7 +2,8 @@ use std::{ fs, io::{self, BufWriter, Write}, os::unix::{ffi::OsStrExt, fs::MetadataExt}, - path, process, + path::PathBuf, + process, string::String, }; @@ -59,7 +60,7 @@ fn main() { let mut result: Vec; - let path = path::PathBuf::from(file); + let path = PathBuf::from(file); if flags.directory || path.is_file() { result = Vec::new(); @@ -67,7 +68,7 @@ fn main() { let item = if flags.directory { File::from_name(BString::from(path.as_os_str().as_bytes()), path, flags) } else { - File::from(path::PathBuf::from(file), flags) + File::from(PathBuf::from(file), flags) }; match item { @@ -91,26 +92,7 @@ fn main() { .collect(); if !flags.no_sort { - if flags.time { - if flags.last_accessed { - result.sort_by_key(sort_by_access_time); - } else if flags.file_status_modification { - result.sort_by_key(sort_by_ctime) - } else { - result.sort_by_key(sort_by_time); - } - result.reverse(); - } else if flags.sort_size { - result.sort_by_key(sort_by_size); - result.reverse(); - } else { - // Sort the directory entries by file name by default - result.sort_by_key(sort_by_name); - } - - if flags.reverse { - result.reverse(); - } + sort(&mut result, &flags); } }, Err(err) => { @@ -125,7 +107,7 @@ fn main() { if !flags.directory && (flags.all || flags.no_sort) { // Retrieve the current directories information. This must // be canonicalize incase the path is relative - let current = path::PathBuf::from(file).canonicalize(); + let current = PathBuf::from(file).canonicalize(); let current = match current { Ok(current) => current, @@ -145,12 +127,14 @@ fn main() { }, }; - // Retrieve the parent path. Default to the current path if the parent doesn't - // exist - let parent_path = - path::PathBuf::from(dot.path.parent().unwrap_or_else(|| current.as_path())); + // Retrieve the parent path. Default to the current path if the + // parent doesn't exist + let parent_path = match dot.path.parent() { + Some(parent) => parent, + None => current.as_path(), + }; - let dot_dot = File::from_name(BString::from(".."), parent_path, flags); + let dot_dot = File::from_name(BString::from(".."), PathBuf::from(parent_path), flags); let dot_dot = match dot_dot { Ok(dot_dot) => dot_dot, @@ -403,6 +387,30 @@ fn print_list(files: Vec, writer: &mut W, flags: Flags) -> io::R Ok(()) } +/// Sort a list of files based on the provided flags. +fn sort(files: &mut Vec, flags: &Flags) { + if flags.time { + if flags.last_accessed { + files.sort_by_key(sort_by_access_time); + } else if flags.file_status_modification { + files.sort_by_key(sort_by_ctime) + } else { + files.sort_by_key(sort_by_time); + } + files.reverse(); + } else if flags.sort_size { + files.sort_by_key(sort_by_size); + files.reverse(); + } else { + // Sort the directory entries by file name by default + files.sort_by_key(sort_by_name); + } + + if flags.reverse { + files.reverse(); + } +} + /// Sort a list of files by last accessed time fn sort_by_access_time(file: &File) -> i64 { file.metadata.atime() } From 00ca7394d7d5c666ab4cabba624832798c006464 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Mon, 16 Nov 2020 14:06:11 -0600 Subject: [PATCH 111/133] Ls: write out bytes if stdout isn't a tty --- ls/src/main.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ls/src/main.rs b/ls/src/main.rs index b3c97462..6adf0fc3 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -176,7 +176,8 @@ fn main() { fn print_default(files: Vec, writer: &mut W, flags: Flags) -> io::Result<()> { if !is_tty(&io::stdout()) { for file in &files { - writeln!(writer, "{}", file.name)?; + writer.write_all(file.name.as_bytes())?; + writeln!(writer)?; } return Ok(()); From 38a6fb2f42c1fbc46325a2c24f0b823c0245030a Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Tue, 17 Nov 2020 12:56:00 -0600 Subject: [PATCH 112/133] Ls: refactor utility output --- ls/src/flags.rs | 9 ++ ls/src/main.rs | 327 +++++++++-------------------------------------- ls/src/output.rs | 238 ++++++++++++++++++++++++++++++++++ 3 files changed, 309 insertions(+), 265 deletions(-) create mode 100644 ls/src/output.rs diff --git a/ls/src/flags.rs b/ls/src/flags.rs index 438d7a2c..00a3e68f 100644 --- a/ls/src/flags.rs +++ b/ls/src/flags.rs @@ -1,3 +1,7 @@ +use std::io; + +use coreutils_core::os::tty::is_tty; + use clap::ArgMatches; /// Represents the command line arguments available to `ls` @@ -85,6 +89,11 @@ impl Flags { && (self.list || self.no_owner || self.no_group || self.numeric_uid_gid) } + pub fn show_grid(&self) -> bool { + !self.comma_separate + && (self.order_left_to_right || self.order_top_to_bottom || is_tty(&io::stdout())) + } + /// Whether or not to show hidden files and directories pub fn show_hidden(&self) -> bool { self.all || self.almost_all || self.no_sort } } diff --git a/ls/src/main.rs b/ls/src/main.rs index 6adf0fc3..3e7580f3 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -7,23 +7,21 @@ use std::{ string::String, }; -use coreutils_core::{ - os::tty::{is_tty, tty_dimensions}, - BString, ByteSlice, -}; - -use term_grid::{Alignment, Cell, Direction, Filling, Grid, GridOptions}; +use coreutils_core::bstr::{BString, ByteSlice}; extern crate chrono; +use term_grid::Direction; + mod cli; mod file; mod flags; +mod output; mod table; -use file::{File, FileColor}; +use file::File; use flags::Flags; -use table::{Row, Table}; +use output::{default, grid, list}; fn main() { let matches = cli::create_app().get_matches(); @@ -38,9 +36,9 @@ fn main() { let multiple = files.len() > 1; for (i, file) in files.enumerate() { - if multiple { - if i == 0 { - match writeln!(writer, "\n") { + if !flags.directory && multiple { + if i != 0 { + match writeln!(writer) { Ok(_) => {}, Err(err) => { eprintln!("ls: {}", err); @@ -102,54 +100,69 @@ fn main() { break; }, } - } - if !flags.directory && (flags.all || flags.no_sort) { - // Retrieve the current directories information. This must - // be canonicalize incase the path is relative - let current = PathBuf::from(file).canonicalize(); + if !flags.directory && (flags.all || flags.no_sort) { + // Retrieve the current directories information. This must + // be canonicalize incase the path is relative + let current = PathBuf::from(file).canonicalize(); - let current = match current { - Ok(current) => current, - Err(err) => { - eprintln!("ls: {}", err); - process::exit(1); - }, - }; + let current = match current { + Ok(current) => current, + Err(err) => { + eprintln!("ls: {}", err); + process::exit(1); + }, + }; - let dot = File::from_name(BString::from("."), current.clone(), flags); + let dot = File::from_name(BString::from("."), current.clone(), flags); - let dot = match dot { - Ok(dot) => dot, - Err(err) => { - eprintln!("ls: {}", err); - process::exit(1); - }, - }; + let dot = match dot { + Ok(dot) => dot, + Err(err) => { + eprintln!("ls: {}", err); + process::exit(1); + }, + }; - // Retrieve the parent path. Default to the current path if the - // parent doesn't exist - let parent_path = match dot.path.parent() { - Some(parent) => parent, - None => current.as_path(), - }; + // Retrieve the parent path. Default to the current path if the + // parent doesn't exist + let parent_path = match dot.path.parent() { + Some(parent) => parent, + None => current.as_path(), + }; + + let dot_dot = + File::from_name(BString::from(".."), PathBuf::from(parent_path), flags); - let dot_dot = File::from_name(BString::from(".."), PathBuf::from(parent_path), flags); + let dot_dot = match dot_dot { + Ok(dot_dot) => dot_dot, + Err(err) => { + eprintln!("ls: {}", err); + process::exit(1); + }, + }; - let dot_dot = match dot_dot { - Ok(dot_dot) => dot_dot, + result.insert(0, dot); + result.insert(1, dot_dot); + } + } + + if flags.show_list() { + match list(result, &mut writer, flags) { + Ok(_) => {}, Err(err) => { - eprintln!("ls: {}", err); - process::exit(1); + eprintln!("ls: cannot access '{}': {}", file, err); + exit_code = 1; }, + } + } else if flags.show_grid() { + let direction = if flags.order_left_to_right && !flags.order_top_to_bottom { + Direction::LeftToRight + } else { + Direction::TopToBottom }; - result.insert(0, dot); - result.insert(1, dot_dot); - } - - if !flags.comma_separate && flags.show_list() { - match print_list(result, &mut writer, flags) { + match grid(result, &mut writer, direction) { Ok(_) => {}, Err(err) => { eprintln!("ls: cannot access '{}': {}", file, err); @@ -157,7 +170,7 @@ fn main() { }, } } else { - match print_default(result, &mut writer, flags) { + match default(result, &mut writer, flags) { Ok(_) => {}, Err(err) => { eprintln!("ls: cannot access '{}': {}", file, err); @@ -172,222 +185,6 @@ fn main() { } } -/// Prints information about a file in the default format -fn print_default(files: Vec, writer: &mut W, flags: Flags) -> io::Result<()> { - if !is_tty(&io::stdout()) { - for file in &files { - writer.write_all(file.name.as_bytes())?; - writeln!(writer)?; - } - - return Ok(()); - } else if flags.comma_separate { - for (i, file) in files.iter().enumerate() { - let file_name = &file.name; - - if (i + 1) == files.len() { - writeln!(writer, "{}", file_name)?; - } else { - write!(writer, "{}, ", file_name)?; - } - } - - return Ok(()); - } else if flags.order_left_to_right && !flags.order_top_to_bottom { - return print_grid(files, writer, Direction::LeftToRight); - } - - print_grid(files, writer, Direction::TopToBottom) -} - -fn print_grid(files: Vec, writer: &mut W, direction: Direction) -> io::Result<()> { - let mut grid = Grid::new(GridOptions { filling: Filling::Spaces(2), direction }); - - let width = match tty_dimensions(&io::stdout()) { - Some(result) => result.0, - None => { - return Err(io::Error::new( - io::ErrorKind::Other, - "Unable to retrieve terminal dimensions.", - )); - }, - }; - - for file in &files { - grid.add(Cell { - alignment: Alignment::Left, - contents: file.file_name(FileColor::Show), - width: file.file_name(FileColor::Hide).len(), - }); - } - - match grid.fit_into_width(width.into()) { - Some(display) => { - write!(writer, "{}", display)?; - Ok(()) - }, - None => { - for file in &files { - writeln!(writer, "{}", file.file_name(FileColor::Show))?; - } - - Ok(()) - }, - } -} - -/// Prints information about the provided file in the long (`-l`) format -fn print_list(files: Vec, writer: &mut W, flags: Flags) -> io::Result<()> { - let mut inode_width = 1; - let mut block_width = 1; - let permissions_width = 1; - let mut hard_links_width = 1; - let mut user_width = 1; - let mut group_width = 1; - let mut size_width = 1; - let time_width = 1; - let file_name_width = 1; - - let mut rows = Table::new(); - - let mut total: u64 = 0; - - for file in &files { - let mut row = Row::new(); - - // Process the file's inode - if flags.inode { - let inode = file.inode(); - let inode_len = inode.len(); - - if inode_len > inode_width { - inode_width = inode_len; - } - - row.inode = inode; - } - - total += file.blocks(); - - // Process the file's block size - if flags.size { - let block = file.blocks() as usize; - let block_len = block.to_string().len(); - - if block_len > block_width { - block_width = block_len; - } - - row.block = block.to_string(); - } - - // Process the file's permissions - let permissions = file.permissions(); - - row.permissions = permissions; - - // Process the file's hard links - let hard_links = file.hard_links(); - let hard_links_len = hard_links.len(); - - if hard_links_len > hard_links_width { - hard_links_width = hard_links_len; - } - - row.hard_links = hard_links; - - // Process the file's user name - if !flags.no_owner { - let user = match file.user() { - Ok(file_user) => file_user, - Err(err) => { - eprintln!("ls: {}", err); - BString::from(file.metadata.uid().to_string()) - }, - }; - - let user_len = user.len(); - - if user_len > user_width { - user_width = user_len; - } - - row.user = user; - } - - // Process the file's group name - if !flags.no_group { - let group = match file.group() { - Ok(file_group) => file_group, - Err(err) => { - eprintln!("ls: {}", err); - BString::from(file.metadata.gid().to_string()) - }, - }; - - let group_len = group.len(); - - if group_len > group_width { - group_width = group_len; - } - - row.group = group; - } - - // Process the file's size - let size = file.size(); - let size_len = file.size().len(); - - if size_len > size_width { - size_width = size_len; - } - - row.size = size; - - // Process the file's timestamp - row.time = file.time(); - - // Process the file's name - row.file_name = file.file_name(FileColor::Show); - - rows.push(row); - } - - writeln!(writer, "total {}", total)?; - - for row in rows { - if flags.inode { - write!(writer, "{:>1$} ", row.inode, inode_width)?; - } - - if flags.size { - write!(writer, "{:>1$} ", row.block, block_width)?; - } - - write!(writer, "{:<1$} ", row.permissions, permissions_width)?; - - write!(writer, "{:>1$} ", row.hard_links, hard_links_width)?; - - if !flags.no_owner { - write!(writer, "{:<1$} ", row.user.to_string(), user_width)?; - } - - if !flags.no_group { - write!(writer, "{:<1$} ", row.group.to_string(), group_width)?; - } - - write!(writer, "{:>1$} ", row.size, size_width)?; - - write!(writer, "{:<1$} ", row.time, time_width)?; - - write!(writer, "{:<1$} ", row.file_name, file_name_width)?; - - writeln!(writer)?; - } - - Ok(()) -} - /// Sort a list of files based on the provided flags. fn sort(files: &mut Vec, flags: &Flags) { if flags.time { diff --git a/ls/src/output.rs b/ls/src/output.rs new file mode 100644 index 00000000..d763d7be --- /dev/null +++ b/ls/src/output.rs @@ -0,0 +1,238 @@ +use std::{ + io::{self, Write}, + os::unix::fs::MetadataExt, +}; + +use coreutils_core::{ + os::tty::{is_tty, tty_dimensions}, + BString, ByteSlice, +}; + +use term_grid::{Alignment, Cell, Direction, Filling, Grid, GridOptions}; + +extern crate chrono; + +use crate::{ + file::{File, FileColor}, + flags::Flags, + table::{Row, Table}, +}; + +/// Writes the provided files in the default format. +pub(crate) fn default(files: Vec, writer: &mut W, flags: Flags) -> io::Result<()> { + if !is_tty(&io::stdout()) { + for file in &files { + writer.write_all(file.name.as_bytes())?; + writeln!(writer)?; + } + + return Ok(()); + } else if flags.comma_separate { + for (i, file) in files.iter().enumerate() { + let file_name = file.file_name(FileColor::Show); + + if (i + 1) == files.len() { + writeln!(writer, "{}", file_name)?; + } else { + write!(writer, "{}, ", file_name)?; + } + } + + return Ok(()); + } + + Err(io::Error::new(io::ErrorKind::Other, "Failed to display files.")) +} + +/// Writes the provided files in a grid format. +pub(crate) fn grid( + files: Vec, writer: &mut W, direction: Direction, +) -> io::Result<()> { + let mut grid = Grid::new(GridOptions { filling: Filling::Spaces(2), direction }); + + let width = match tty_dimensions(&io::stdout()) { + Some(result) => result.0, + None => { + return Err(io::Error::new( + io::ErrorKind::Other, + "Unable to retrieve terminal dimensions.", + )); + }, + }; + + for file in &files { + grid.add(Cell { + alignment: Alignment::Left, + contents: file.file_name(FileColor::Show), + width: file.file_name(FileColor::Hide).len(), + }); + } + + match grid.fit_into_width(width.into()) { + Some(display) => { + write!(writer, "{}", display)?; + Ok(()) + }, + None => { + for file in &files { + writeln!(writer, "{}", file.file_name(FileColor::Show))?; + } + + Ok(()) + }, + } +} + +/// Writes the provided files in a list format. +pub(crate) fn list(files: Vec, writer: &mut W, flags: Flags) -> io::Result<()> { + let mut inode_width = 1; + let mut block_width = 1; + let permissions_width = 1; + let mut hard_links_width = 1; + let mut user_width = 1; + let mut group_width = 1; + let mut size_width = 1; + let time_width = 1; + let file_name_width = 1; + + let mut rows = Table::new(); + + let mut total: u64 = 0; + + for file in &files { + let mut row = Row::new(); + + // Process the file's inode + if flags.inode { + let inode = file.inode(); + let inode_len = inode.len(); + + if inode_len > inode_width { + inode_width = inode_len; + } + + row.inode = inode; + } + + total += file.blocks(); + + // Process the file's block size + if flags.size { + let block = file.blocks() as usize; + let block_len = block.to_string().len(); + + if block_len > block_width { + block_width = block_len; + } + + row.block = block.to_string(); + } + + // Process the file's permissions + let permissions = file.permissions(); + + row.permissions = permissions; + + // Process the file's hard links + let hard_links = file.hard_links(); + let hard_links_len = hard_links.len(); + + if hard_links_len > hard_links_width { + hard_links_width = hard_links_len; + } + + row.hard_links = hard_links; + + // Process the file's user name + if !flags.no_owner { + let user = match file.user() { + Ok(file_user) => file_user, + Err(err) => { + eprintln!("ls: {}", err); + BString::from(file.metadata.uid().to_string()) + }, + }; + + let user_len = user.len(); + + if user_len > user_width { + user_width = user_len; + } + + row.user = user; + } + + // Process the file's group name + if !flags.no_group { + let group = match file.group() { + Ok(file_group) => file_group, + Err(err) => { + eprintln!("ls: {}", err); + BString::from(file.metadata.gid().to_string()) + }, + }; + + let group_len = group.len(); + + if group_len > group_width { + group_width = group_len; + } + + row.group = group; + } + + // Process the file's size + let size = file.size(); + let size_len = file.size().len(); + + if size_len > size_width { + size_width = size_len; + } + + row.size = size; + + // Process the file's timestamp + row.time = file.time(); + + // Process the file's name + row.file_name = file.file_name(FileColor::Show); + + rows.push(row); + } + + if !flags.directory { + writeln!(writer, "total {}", total)?; + } + + for row in rows { + if flags.inode { + write!(writer, "{:>1$} ", row.inode, inode_width)?; + } + + if flags.size { + write!(writer, "{:>1$} ", row.block, block_width)?; + } + + write!(writer, "{:<1$} ", row.permissions, permissions_width)?; + + write!(writer, "{:>1$} ", row.hard_links, hard_links_width)?; + + if !flags.no_owner { + write!(writer, "{:<1$} ", row.user.to_string(), user_width)?; + } + + if !flags.no_group { + write!(writer, "{:<1$} ", row.group.to_string(), group_width)?; + } + + write!(writer, "{:>1$} ", row.size, size_width)?; + + write!(writer, "{:<1$} ", row.time, time_width)?; + + write!(writer, "{:<1$} ", row.file_name, file_name_width)?; + + writeln!(writer)?; + } + + Ok(()) +} From 58fc379fc09046196f2d92df5ea75513c9bb51cf Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Wed, 18 Nov 2020 17:35:46 -0600 Subject: [PATCH 113/133] Ls: implement -1 flag --- ls/src/cli.rs | 16 +++++++++++----- ls/src/flags.rs | 4 ++++ ls/src/output.rs | 8 ++++++++ 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/ls/src/cli.rs b/ls/src/cli.rs index a959f612..68e19f62 100644 --- a/ls/src/cli.rs +++ b/ls/src/cli.rs @@ -43,13 +43,13 @@ pub(crate) fn create_app<'a, 'b>() -> App<'a, 'b> { modification of the file itself for sorting -t or writing -l.", ) .short("c") - .long("file_status_modification"), + .long("file-status-modification"), ) .arg( Arg::with_name("order_top_to_bottom") .help("Write multi-text-column output with entries sorted down the columns.") .short("C") - .long("order_top_to_bottom"), + .long("order-top-to-bottom"), ) .arg( Arg::with_name("directory") @@ -79,13 +79,13 @@ pub(crate) fn create_app<'a, 'b>() -> App<'a, 'b> { written for the -l, -n, -s, -g, and -o options to 1024 bytes.", ) .short("k") - .long("block_size"), + .long("block-size"), ) .arg( Arg::with_name("comma_separate") .help("Fill width with a comma separated list of entries.") .short("m") - .long("comma_separate"), + .long("comma-separate"), ) .arg( Arg::with_name("dereference") @@ -164,6 +164,12 @@ pub(crate) fn create_app<'a, 'b>() -> App<'a, 'b> { Arg::with_name("order_left_to_right") .help("Sort columns left to right.") .short("x") - .long("order_left_to_right"), + .long("order-left-to-right"), + ) + .arg( + Arg::with_name("one_per_line") + .help("Force output to be one entry per line.") + .short("1") + .long("one-per-line"), ) } diff --git a/ls/src/flags.rs b/ls/src/flags.rs index 00a3e68f..7ee8eb9a 100644 --- a/ls/src/flags.rs +++ b/ls/src/flags.rs @@ -23,6 +23,7 @@ pub(crate) struct Flags { pub no_owner: bool, pub no_sort: bool, pub numeric_uid_gid: bool, + pub one_per_line: bool, pub order_left_to_right: bool, pub order_top_to_bottom: bool, pub reverse: bool, @@ -50,6 +51,7 @@ impl Flags { let no_owner = matches.is_present("no_owner"); let no_sort = matches.is_present("no_sort"); let numeric_uid_gid = matches.is_present("numeric_uid_gid"); + let one_per_line = matches.is_present("one_per_line"); let order_left_to_right = matches.is_present("order_left_to_right"); let order_top_to_bottom = matches.is_present("order_top_to_bottom"); let reverse = matches.is_present("reverse"); @@ -74,6 +76,7 @@ impl Flags { no_owner, no_sort, numeric_uid_gid, + one_per_line, order_left_to_right, order_top_to_bottom, reverse, @@ -91,6 +94,7 @@ impl Flags { pub fn show_grid(&self) -> bool { !self.comma_separate + && !self.one_per_line && (self.order_left_to_right || self.order_top_to_bottom || is_tty(&io::stdout())) } diff --git a/ls/src/output.rs b/ls/src/output.rs index d763d7be..d8ce9f6c 100644 --- a/ls/src/output.rs +++ b/ls/src/output.rs @@ -26,6 +26,14 @@ pub(crate) fn default(files: Vec, writer: &mut W, flags: Flags) writeln!(writer)?; } + return Ok(()); + } else if flags.one_per_line { + for file in &files { + let file_name = file.file_name(FileColor::Show); + + writeln!(writer, "{}", file_name)?; + } + return Ok(()); } else if flags.comma_separate { for (i, file) in files.iter().enumerate() { From f7e2f9c2092a1e72fe3709a41ffc47b864333926 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Thu, 19 Nov 2020 09:16:37 -0600 Subject: [PATCH 114/133] Ls: clean up --- ls/src/file.rs | 7 +++---- ls/src/main.rs | 10 +++++----- ls/src/output.rs | 10 ++++------ 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/ls/src/file.rs b/ls/src/file.rs index 92780d5a..f02ccfbb 100644 --- a/ls/src/file.rs +++ b/ls/src/file.rs @@ -32,6 +32,8 @@ pub(crate) enum FileColor { Hide, } +pub(crate) type Files = Vec; + /// Represents a file and it's properties pub(crate) struct File { pub name: BString, @@ -180,13 +182,10 @@ impl File { /// Will return `Error` if the path terminates at '..' or if the file name /// contains invalid unicode characters. pub fn path_buf_to_file_name(path: &path::PathBuf) -> io::Result { - // Create a new IO Error. - let io_error = |kind: io::ErrorKind, msg: &str| io::Error::new(kind, msg); - let file_name = match path.file_name() { Some(file_name) => file_name, None => { - return Err(io_error(io::ErrorKind::NotFound, "Path terminates at \"..\"")); + return Err(io::Error::new(io::ErrorKind::NotFound, "Path terminates in \"..\"")); }, }; diff --git a/ls/src/main.rs b/ls/src/main.rs index 3e7580f3..47f5dae3 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -19,7 +19,7 @@ mod flags; mod output; mod table; -use file::File; +use file::{File, Files}; use flags::Flags; use output::{default, grid, list}; @@ -56,7 +56,7 @@ fn main() { } } - let mut result: Vec; + let mut result: Files; let path = PathBuf::from(file); @@ -186,12 +186,12 @@ fn main() { } /// Sort a list of files based on the provided flags. -fn sort(files: &mut Vec, flags: &Flags) { +fn sort(files: &mut Files, flags: &Flags) { if flags.time { if flags.last_accessed { files.sort_by_key(sort_by_access_time); } else if flags.file_status_modification { - files.sort_by_key(sort_by_ctime) + files.sort_by_key(sort_by_last_changed_time) } else { files.sort_by_key(sort_by_time); } @@ -213,7 +213,7 @@ fn sort(files: &mut Vec, flags: &Flags) { fn sort_by_access_time(file: &File) -> i64 { file.metadata.atime() } /// Sort a list of files by last change of file status information -fn sort_by_ctime(file: &File) -> i64 { file.metadata.ctime() } +fn sort_by_last_changed_time(file: &File) -> i64 { file.metadata.ctime() } /// Sort a list of files by file name alphabetically fn sort_by_name(file: &File) -> String { diff --git a/ls/src/output.rs b/ls/src/output.rs index d8ce9f6c..b8558ce4 100644 --- a/ls/src/output.rs +++ b/ls/src/output.rs @@ -13,13 +13,13 @@ use term_grid::{Alignment, Cell, Direction, Filling, Grid, GridOptions}; extern crate chrono; use crate::{ - file::{File, FileColor}, + file::{FileColor, Files}, flags::Flags, table::{Row, Table}, }; /// Writes the provided files in the default format. -pub(crate) fn default(files: Vec, writer: &mut W, flags: Flags) -> io::Result<()> { +pub(crate) fn default(files: Files, writer: &mut W, flags: Flags) -> io::Result<()> { if !is_tty(&io::stdout()) { for file in &files { writer.write_all(file.name.as_bytes())?; @@ -53,9 +53,7 @@ pub(crate) fn default(files: Vec, writer: &mut W, flags: Flags) } /// Writes the provided files in a grid format. -pub(crate) fn grid( - files: Vec, writer: &mut W, direction: Direction, -) -> io::Result<()> { +pub(crate) fn grid(files: Files, writer: &mut W, direction: Direction) -> io::Result<()> { let mut grid = Grid::new(GridOptions { filling: Filling::Spaces(2), direction }); let width = match tty_dimensions(&io::stdout()) { @@ -92,7 +90,7 @@ pub(crate) fn grid( } /// Writes the provided files in a list format. -pub(crate) fn list(files: Vec, writer: &mut W, flags: Flags) -> io::Result<()> { +pub(crate) fn list(files: Files, writer: &mut W, flags: Flags) -> io::Result<()> { let mut inode_width = 1; let mut block_width = 1; let permissions_width = 1; From 6b9136e41eca645a2b32811c1595cbead93a9a0e Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Thu, 19 Nov 2020 09:18:46 -0600 Subject: [PATCH 115/133] Ls: fix comment --- ls/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ls/src/main.rs b/ls/src/main.rs index 47f5dae3..3bc3c7f2 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -103,7 +103,7 @@ fn main() { if !flags.directory && (flags.all || flags.no_sort) { // Retrieve the current directories information. This must - // be canonicalize incase the path is relative + // be canonicalized in case the path is relative. let current = PathBuf::from(file).canonicalize(); let current = match current { From 661e807f8455ad1493030ccb243ccefbdfbb00ba Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Thu, 19 Nov 2020 15:25:58 -0600 Subject: [PATCH 116/133] Ls: implement -H flag --- ls/src/cli.rs | 6 ++++++ ls/src/file.rs | 31 +++++++++++++++++++------------ ls/src/flags.rs | 3 +++ 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/ls/src/cli.rs b/ls/src/cli.rs index 68e19f62..6e5ea957 100644 --- a/ls/src/cli.rs +++ b/ls/src/cli.rs @@ -72,6 +72,12 @@ pub(crate) fn create_app<'a, 'b>() -> App<'a, 'b> { .short("F") .long("classify"), ) + .arg( + Arg::with_name("no_dereference") + .help("Follow symbolic links listed on the command line.") + .short("H") + .long("no-dereference"), + ) .arg( Arg::with_name("block_size") .help( diff --git a/ls/src/file.rs b/ls/src/file.rs index f02ccfbb..e7695008 100644 --- a/ls/src/file.rs +++ b/ls/src/file.rs @@ -13,7 +13,7 @@ use std::{ ffi::OsStrExt, fs::{FileTypeExt, MetadataExt, PermissionsExt}, }, - path, + path::PathBuf, result::Result, string::String, }; @@ -37,22 +37,22 @@ pub(crate) type Files = Vec; /// Represents a file and it's properties pub(crate) struct File { pub name: BString, - pub path: path::PathBuf, + pub path: PathBuf, pub metadata: fs::Metadata, flags: Flags, } impl File { - /// Creates a `File` instance from a `DirEntry` - pub fn from(path: path::PathBuf, flags: Flags) -> io::Result { - let metadata = path.symlink_metadata()?; + /// Creates a `File` instance from a `PathBuf` + pub fn from(path: PathBuf, flags: Flags) -> io::Result { + let metadata = File::metadata(&path, &flags)?; if flags.dereference && metadata.file_type().is_symlink() { - let symlink = fs::read_link(path.clone())?; + let symlink = fs::read_link(path)?; let name = File::path_buf_to_file_name(&symlink)?; - let metadata = path.metadata()?; + let metadata = File::metadata(&symlink, &flags)?; return Ok(File { name, path: symlink, metadata, flags }); } @@ -62,9 +62,9 @@ impl File { Ok(File { name, path, metadata, flags }) } - /// Creates a `File` instance from a `DirEntry` and supplies a file name - pub fn from_name(name: BString, path: path::PathBuf, flags: Flags) -> io::Result { - let metadata = path.metadata()?; + /// Creates a `File` instance from a `PathBuf` and supplies a file name + pub fn from_name(name: BString, path: PathBuf, flags: Flags) -> io::Result { + let metadata = File::metadata(&path, &flags)?; Ok(File { name, path, metadata, flags }) } @@ -162,7 +162,7 @@ impl File { } /// Check if a path is an executable file - pub fn is_executable(path: &path::PathBuf) -> bool { + pub fn is_executable(path: &PathBuf) -> bool { let mut result = false; let metadata = fs::symlink_metadata(path); @@ -181,7 +181,7 @@ impl File { /// /// Will return `Error` if the path terminates at '..' or if the file name /// contains invalid unicode characters. - pub fn path_buf_to_file_name(path: &path::PathBuf) -> io::Result { + pub fn path_buf_to_file_name(path: &PathBuf) -> io::Result { let file_name = match path.file_name() { Some(file_name) => file_name, None => { @@ -192,6 +192,13 @@ impl File { Ok(BString::from(file_name.as_bytes())) } + /// Retrieves the metadata from a `PathBuf`. + /// + /// Symbolic links will be followed if the `-H` flag is present. + pub fn metadata(path: &PathBuf, flags: &Flags) -> io::Result { + if flags.no_dereference { fs::metadata(path) } else { fs::symlink_metadata(path) } + } + /// Gets a file name from a directory entry and adds appropriate formatting pub fn file_name(&self, color: FileColor) -> String { // Determine if the file name should have a color applied. diff --git a/ls/src/flags.rs b/ls/src/flags.rs index 7ee8eb9a..42120e88 100644 --- a/ls/src/flags.rs +++ b/ls/src/flags.rs @@ -19,6 +19,7 @@ pub(crate) struct Flags { pub inode: bool, pub last_accessed: bool, pub list: bool, + pub no_dereference: bool, pub no_group: bool, pub no_owner: bool, pub no_sort: bool, @@ -47,6 +48,7 @@ impl Flags { let inode = matches.is_present("inode"); let last_accessed = matches.is_present("last_accessed"); let list = matches.is_present("list"); + let no_dereference = matches.is_present("no_dereference"); let no_group = matches.is_present("no_group"); let no_owner = matches.is_present("no_owner"); let no_sort = matches.is_present("no_sort"); @@ -72,6 +74,7 @@ impl Flags { indicator, last_accessed, list, + no_dereference, no_group, no_owner, no_sort, From 1afe7b518d526630a289843e52455f7af2bede08 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Thu, 19 Nov 2020 15:26:47 -0600 Subject: [PATCH 117/133] Ls: remove extra whitespace --- ls/src/file.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ls/src/file.rs b/ls/src/file.rs index e7695008..33bdec5c 100644 --- a/ls/src/file.rs +++ b/ls/src/file.rs @@ -193,7 +193,7 @@ impl File { } /// Retrieves the metadata from a `PathBuf`. - /// + /// /// Symbolic links will be followed if the `-H` flag is present. pub fn metadata(path: &PathBuf, flags: &Flags) -> io::Result { if flags.no_dereference { fs::metadata(path) } else { fs::symlink_metadata(path) } From e90fc043604c6274327d2a33fb644fcf1e21a18e Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Thu, 19 Nov 2020 15:27:38 -0600 Subject: [PATCH 118/133] Ls: display std crates at the top --- ls/src/file.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/ls/src/file.rs b/ls/src/file.rs index 33bdec5c..03802948 100644 --- a/ls/src/file.rs +++ b/ls/src/file.rs @@ -1,12 +1,3 @@ -use coreutils_core::{ - os::{ - group::{Error as GroupError, Group}, - passwd::{Error as PasswdError, Passwd}, - tty::is_tty, - }, - BStr, BString, -}; - use std::{ fs, io, os::unix::{ @@ -18,6 +9,15 @@ use std::{ string::String, }; +use coreutils_core::{ + os::{ + group::{Error as GroupError, Group}, + passwd::{Error as PasswdError, Passwd}, + tty::is_tty, + }, + BStr, BString, +}; + use ansi_term::Color; extern crate chrono; From f193776a3158e288267bab4a94d8d1485246ec48 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Thu, 19 Nov 2020 15:33:27 -0600 Subject: [PATCH 119/133] Ls: fix comment --- ls/src/file.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ls/src/file.rs b/ls/src/file.rs index 03802948..6b3c351e 100644 --- a/ls/src/file.rs +++ b/ls/src/file.rs @@ -179,8 +179,7 @@ impl File { /// Gets the file name from a `PathBuf` /// - /// Will return `Error` if the path terminates at '..' or if the file name - /// contains invalid unicode characters. + /// Will return `Error` if the path terminates at '..'. pub fn path_buf_to_file_name(path: &PathBuf) -> io::Result { let file_name = match path.file_name() { Some(file_name) => file_name, From 869f22faf08e5ba0222274b3b5422e2f64ab35c8 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Thu, 19 Nov 2020 15:50:39 -0600 Subject: [PATCH 120/133] Ls: remove unneeded with values --- ls/src/output.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/ls/src/output.rs b/ls/src/output.rs index b8558ce4..e73689a5 100644 --- a/ls/src/output.rs +++ b/ls/src/output.rs @@ -93,13 +93,10 @@ pub(crate) fn grid(files: Files, writer: &mut W, direction: Direction) pub(crate) fn list(files: Files, writer: &mut W, flags: Flags) -> io::Result<()> { let mut inode_width = 1; let mut block_width = 1; - let permissions_width = 1; let mut hard_links_width = 1; let mut user_width = 1; let mut group_width = 1; let mut size_width = 1; - let time_width = 1; - let file_name_width = 1; let mut rows = Table::new(); @@ -219,7 +216,7 @@ pub(crate) fn list(files: Files, writer: &mut W, flags: Flags) -> io:: write!(writer, "{:>1$} ", row.block, block_width)?; } - write!(writer, "{:<1$} ", row.permissions, permissions_width)?; + write!(writer, "{:<1} ", row.permissions)?; write!(writer, "{:>1$} ", row.hard_links, hard_links_width)?; @@ -233,9 +230,9 @@ pub(crate) fn list(files: Files, writer: &mut W, flags: Flags) -> io:: write!(writer, "{:>1$} ", row.size, size_width)?; - write!(writer, "{:<1$} ", row.time, time_width)?; + write!(writer, "{:<1} ", row.time)?; - write!(writer, "{:<1$} ", row.file_name, file_name_width)?; + write!(writer, "{:<1} ", row.file_name)?; writeln!(writer)?; } From 37b74be2cee948c1291f11ffd886f13f5c40f28f Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Thu, 19 Nov 2020 16:09:06 -0600 Subject: [PATCH 121/133] Ls: clean up --- ls/src/output.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/ls/src/output.rs b/ls/src/output.rs index e73689a5..b755fcfc 100644 --- a/ls/src/output.rs +++ b/ls/src/output.rs @@ -217,7 +217,6 @@ pub(crate) fn list(files: Files, writer: &mut W, flags: Flags) -> io:: } write!(writer, "{:<1} ", row.permissions)?; - write!(writer, "{:>1$} ", row.hard_links, hard_links_width)?; if !flags.no_owner { @@ -229,9 +228,7 @@ pub(crate) fn list(files: Files, writer: &mut W, flags: Flags) -> io:: } write!(writer, "{:>1$} ", row.size, size_width)?; - write!(writer, "{:<1} ", row.time)?; - write!(writer, "{:<1} ", row.file_name)?; writeln!(writer)?; From 1bf09018b13f2429985e94152912b9b14692bbd2 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Fri, 20 Nov 2020 08:29:01 -0600 Subject: [PATCH 122/133] Ls: implement the -q flag --- ls/src/cli.rs | 10 ++++++++++ ls/src/flags.rs | 3 +++ ls/src/output.rs | 8 ++++++-- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/ls/src/cli.rs b/ls/src/cli.rs index 6e5ea957..55750699 100644 --- a/ls/src/cli.rs +++ b/ls/src/cli.rs @@ -142,6 +142,16 @@ pub(crate) fn create_app<'a, 'b>() -> App<'a, 'b> { .short("o") .long("no-group"), ) + .arg( + Arg::with_name("hide_control_chars") + .help( + "Force each instance of non-printable filename characters to be written as \ + the � character. This is the default behavior if the output is to a terminal \ + device.", + ) + .short("q") + .long("hide-control-chars"), + ) .arg( Arg::with_name("reverse") .help("Reverse order while sorting.") diff --git a/ls/src/flags.rs b/ls/src/flags.rs index 42120e88..6d3ae17d 100644 --- a/ls/src/flags.rs +++ b/ls/src/flags.rs @@ -15,6 +15,7 @@ pub(crate) struct Flags { pub directory: bool, pub dereference: bool, pub file_status_modification: bool, + pub hide_control_chars: bool, pub indicator: bool, pub inode: bool, pub last_accessed: bool, @@ -44,6 +45,7 @@ impl Flags { let dereference = matches.is_present("dereference"); let directory = matches.is_present("directory"); let file_status_modification = matches.is_present("file_status_modification"); + let hide_control_chars = matches.is_present("hide_control_chars"); let indicator = matches.is_present("indicator"); let inode = matches.is_present("inode"); let last_accessed = matches.is_present("last_accessed"); @@ -70,6 +72,7 @@ impl Flags { directory, dereference, file_status_modification, + hide_control_chars, inode, indicator, last_accessed, diff --git a/ls/src/output.rs b/ls/src/output.rs index b755fcfc..4d245f97 100644 --- a/ls/src/output.rs +++ b/ls/src/output.rs @@ -22,8 +22,12 @@ use crate::{ pub(crate) fn default(files: Files, writer: &mut W, flags: Flags) -> io::Result<()> { if !is_tty(&io::stdout()) { for file in &files { - writer.write_all(file.name.as_bytes())?; - writeln!(writer)?; + if flags.hide_control_chars { + writeln!(writer, "{}", file.name)?; + } else { + writer.write_all(file.name.as_bytes())?; + writeln!(writer)?; + } } return Ok(()); From 1b452c7f5ae66c3a1a20aafda41bfabd32957970 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Fri, 20 Nov 2020 15:51:05 -0600 Subject: [PATCH 123/133] Ls: align output when using the -d flag --- ls/src/main.rs | 222 ++++++++++++++++++++++------------------------- ls/src/output.rs | 38 ++++++++ 2 files changed, 142 insertions(+), 118 deletions(-) diff --git a/ls/src/main.rs b/ls/src/main.rs index 3bc3c7f2..50df61c9 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -11,8 +11,6 @@ use coreutils_core::bstr::{BString, ByteSlice}; extern crate chrono; -use term_grid::Direction; - mod cli; mod file; mod flags; @@ -21,7 +19,7 @@ mod table; use file::{File, Files}; use flags::Flags; -use output::{default, grid, list}; +use output::output; fn main() { let matches = cli::create_app().get_matches(); @@ -33,150 +31,138 @@ fn main() { let mut writer = BufWriter::new(io::stdout()); - let multiple = files.len() > 1; - - for (i, file) in files.enumerate() { - if !flags.directory && multiple { - if i != 0 { - match writeln!(writer) { - Ok(_) => {}, - Err(err) => { - eprintln!("ls: {}", err); - process::exit(1); - }, - } - } - - match writeln!(writer, "{}:", file) { - Ok(_) => {}, - Err(err) => { - eprintln!("ls: {}", err); - process::exit(1); - }, - } - } - - let mut result: Files; + if flags.directory { + let mut result = Files::new(); - let path = PathBuf::from(file); + for file in files { + let path = PathBuf::from(file); - if flags.directory || path.is_file() { - result = Vec::new(); - - let item = if flags.directory { - File::from_name(BString::from(path.as_os_str().as_bytes()), path, flags) - } else { - File::from(PathBuf::from(file), flags) - }; + let item = File::from_name(BString::from(path.as_os_str().as_bytes()), path, flags); match item { Ok(item) => { result.push(item); }, Err(err) => { - eprintln!("ls: cannot access {}: {}", err, file); + eprintln!("ls: cannot access '{}': {}", file, err); process::exit(1); }, } - } else { - match fs::read_dir(file) { - Ok(dir) => { - result = dir - // Collect information about the file or directory - .map(|entry| File::from(entry.unwrap().path(), flags).unwrap()) - // Hide hidden files and directories if `-a` or `-A` flags - // weren't provided - .filter(|file| !File::is_hidden(&file.name.as_bstr()) || flags.show_hidden()) - .collect(); - - if !flags.no_sort { - sort(&mut result, &flags); - } - }, - Err(err) => { - eprintln!("ls: cannot access '{}': {}", file, err); - exit_code = 1; + } - break; - }, - } + sort(&mut result, &flags); - if !flags.directory && (flags.all || flags.no_sort) { - // Retrieve the current directories information. This must - // be canonicalized in case the path is relative. - let current = PathBuf::from(file).canonicalize(); + exit_code = output(result, &mut writer, flags); + } else { + let multiple = files.len() > 1; + + for (i, file) in files.enumerate() { + if multiple { + if i != 0 { + match writeln!(writer) { + Ok(_) => {}, + Err(err) => { + eprintln!("ls: {}", err); + process::exit(1); + }, + } + } - let current = match current { - Ok(current) => current, + match writeln!(writer, "{}:", file) { + Ok(_) => {}, Err(err) => { eprintln!("ls: {}", err); process::exit(1); }, - }; - - let dot = File::from_name(BString::from("."), current.clone(), flags); + } + } - let dot = match dot { - Ok(dot) => dot, - Err(err) => { - eprintln!("ls: {}", err); - process::exit(1); - }, - }; + let mut result = Files::new(); - // Retrieve the parent path. Default to the current path if the - // parent doesn't exist - let parent_path = match dot.path.parent() { - Some(parent) => parent, - None => current.as_path(), - }; + let path = PathBuf::from(file); - let dot_dot = - File::from_name(BString::from(".."), PathBuf::from(parent_path), flags); + if path.is_file() { + let item = File::from(PathBuf::from(file), flags); - let dot_dot = match dot_dot { - Ok(dot_dot) => dot_dot, + match item { + Ok(item) => { + result.push(item); + }, Err(err) => { - eprintln!("ls: {}", err); + eprintln!("ls: cannot access {}: {}", err, file); process::exit(1); }, - }; + } + } else { + match fs::read_dir(file) { + Ok(dir) => { + result = dir + // Collect information about the file or directory + .map(|entry| File::from(entry.unwrap().path(), flags).unwrap()) + // Hide hidden files and directories if `-a` or `-A` flags + // weren't provided + .filter(|file| !File::is_hidden(&file.name.as_bstr()) || flags.show_hidden()) + .collect(); + + if !flags.no_sort { + sort(&mut result, &flags); + } + }, + Err(err) => { + eprintln!("ls: cannot access '{}': {}", file, err); + exit_code = 1; - result.insert(0, dot); - result.insert(1, dot_dot); - } - } + break; + }, + } - if flags.show_list() { - match list(result, &mut writer, flags) { - Ok(_) => {}, - Err(err) => { - eprintln!("ls: cannot access '{}': {}", file, err); - exit_code = 1; - }, + if !flags.directory && (flags.all || flags.no_sort) { + // Retrieve the current directories information. This must + // be canonicalized in case the path is relative. + let current = PathBuf::from(file).canonicalize(); + + let current = match current { + Ok(current) => current, + Err(err) => { + eprintln!("ls: {}", err); + process::exit(1); + }, + }; + + let dot = File::from_name(BString::from("."), current.clone(), flags); + + let dot = match dot { + Ok(dot) => dot, + Err(err) => { + eprintln!("ls: {}", err); + process::exit(1); + }, + }; + + // Retrieve the parent path. Default to the current path if the + // parent doesn't exist + let parent_path = match dot.path.parent() { + Some(parent) => parent, + None => current.as_path(), + }; + + let dot_dot = + File::from_name(BString::from(".."), PathBuf::from(parent_path), flags); + + let dot_dot = match dot_dot { + Ok(dot_dot) => dot_dot, + Err(err) => { + eprintln!("ls: {}", err); + process::exit(1); + }, + }; + + result.insert(0, dot); + result.insert(1, dot_dot); + } } - } else if flags.show_grid() { - let direction = if flags.order_left_to_right && !flags.order_top_to_bottom { - Direction::LeftToRight - } else { - Direction::TopToBottom - }; - match grid(result, &mut writer, direction) { - Ok(_) => {}, - Err(err) => { - eprintln!("ls: cannot access '{}': {}", file, err); - exit_code = 1; - }, - } - } else { - match default(result, &mut writer, flags) { - Ok(_) => {}, - Err(err) => { - eprintln!("ls: cannot access '{}': {}", file, err); - exit_code = 1; - }, - } + exit_code = output(result, &mut writer, flags); } } diff --git a/ls/src/output.rs b/ls/src/output.rs index 4d245f97..3fceb51e 100644 --- a/ls/src/output.rs +++ b/ls/src/output.rs @@ -18,6 +18,44 @@ use crate::{ table::{Row, Table}, }; +pub(crate) fn output(result: Files, writer: &mut W, flags: Flags) -> i32 { + let mut exit_code = 0; + + if flags.show_list() { + match list(result, writer, flags) { + Ok(_) => {}, + Err(err) => { + eprintln!("ls: {}", err); + exit_code = 1; + }, + } + } else if flags.show_grid() { + let direction = if flags.order_left_to_right && !flags.order_top_to_bottom { + Direction::LeftToRight + } else { + Direction::TopToBottom + }; + + match grid(result, writer, direction) { + Ok(_) => {}, + Err(err) => { + eprintln!("ls: {}", err); + exit_code = 1; + }, + } + } else { + match default(result, writer, flags) { + Ok(_) => {}, + Err(err) => { + eprintln!("ls: {}", err); + exit_code = 1; + }, + } + } + + exit_code +} + /// Writes the provided files in the default format. pub(crate) fn default(files: Files, writer: &mut W, flags: Flags) -> io::Result<()> { if !is_tty(&io::stdout()) { From 8a02a57d9e7d1e5ccf78532c5efa6a6ad1b3a588 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Mon, 23 Nov 2020 12:19:45 -0600 Subject: [PATCH 124/133] Ls: remove unwrap usage --- ls/src/main.rs | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/ls/src/main.rs b/ls/src/main.rs index 50df61c9..2a3b9fe5 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -96,13 +96,15 @@ fn main() { } else { match fs::read_dir(file) { Ok(dir) => { - result = dir - // Collect information about the file or directory - .map(|entry| File::from(entry.unwrap().path(), flags).unwrap()) - // Hide hidden files and directories if `-a` or `-A` flags - // weren't provided - .filter(|file| !File::is_hidden(&file.name.as_bstr()) || flags.show_hidden()) - .collect(); + result = match collect(dir, &flags) { + Ok(files) => files, + Err(err) => { + eprintln!("ls: cannot access '{}': {}", file, err); + exit_code = 1; + + break; + }, + }; if !flags.no_sort { sort(&mut result, &flags); @@ -171,6 +173,23 @@ fn main() { } } +/// Collect the results from reading a directory into a `File` collection. +fn collect(dir: fs::ReadDir, flags: &Flags) -> io::Result { + let mut files = Files::new(); + + for entry in dir { + let entry = entry?; + + let file = File::from(entry.path(), *flags)?; + + if !File::is_hidden(&file.name.as_bstr()) || flags.show_hidden() { + files.push(file); + } + } + + Ok(files) +} + /// Sort a list of files based on the provided flags. fn sort(files: &mut Files, flags: &Flags) { if flags.time { From 5a7377a8a8349ad17281b0f3fc9d6725d35fd182 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Mon, 30 Nov 2020 09:30:33 -0600 Subject: [PATCH 125/133] Ls: begin -R flag --- ls/src/cli.rs | 6 ++ ls/src/flags.rs | 3 + ls/src/main.rs | 158 ++++++++++++++++++++++++------------------------ 3 files changed, 89 insertions(+), 78 deletions(-) diff --git a/ls/src/cli.rs b/ls/src/cli.rs index 55750699..ee322cae 100644 --- a/ls/src/cli.rs +++ b/ls/src/cli.rs @@ -158,6 +158,12 @@ pub(crate) fn create_app<'a, 'b>() -> App<'a, 'b> { .short("r") .long("reverse"), ) + .arg( + Arg::with_name("recursive") + .help("Recursively print subdirectories.") + .short("R") + .long("recursive"), + ) .arg( Arg::with_name("size") .help("Print the allocated size of each file, in blocks.") diff --git a/ls/src/flags.rs b/ls/src/flags.rs index 6d3ae17d..f78e8f56 100644 --- a/ls/src/flags.rs +++ b/ls/src/flags.rs @@ -28,6 +28,7 @@ pub(crate) struct Flags { pub one_per_line: bool, pub order_left_to_right: bool, pub order_top_to_bottom: bool, + pub recursive: bool, pub reverse: bool, pub size: bool, pub sort_size: bool, @@ -58,6 +59,7 @@ impl Flags { let one_per_line = matches.is_present("one_per_line"); let order_left_to_right = matches.is_present("order_left_to_right"); let order_top_to_bottom = matches.is_present("order_top_to_bottom"); + let recursive = matches.is_present("recursive"); let reverse = matches.is_present("reverse"); let size = matches.is_present("size"); let sort_size = matches.is_present("sort_size"); @@ -85,6 +87,7 @@ impl Flags { one_per_line, order_left_to_right, order_top_to_bottom, + recursive, reverse, size, sort_size, diff --git a/ls/src/main.rs b/ls/src/main.rs index 2a3b9fe5..4146adc8 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -53,6 +53,8 @@ fn main() { sort(&mut result, &flags); exit_code = output(result, &mut writer, flags); + } else if flags.recursive { + todo!(); } else { let multiple = files.len() > 1; @@ -94,74 +96,7 @@ fn main() { }, } } else { - match fs::read_dir(file) { - Ok(dir) => { - result = match collect(dir, &flags) { - Ok(files) => files, - Err(err) => { - eprintln!("ls: cannot access '{}': {}", file, err); - exit_code = 1; - - break; - }, - }; - - if !flags.no_sort { - sort(&mut result, &flags); - } - }, - Err(err) => { - eprintln!("ls: cannot access '{}': {}", file, err); - exit_code = 1; - - break; - }, - } - - if !flags.directory && (flags.all || flags.no_sort) { - // Retrieve the current directories information. This must - // be canonicalized in case the path is relative. - let current = PathBuf::from(file).canonicalize(); - - let current = match current { - Ok(current) => current, - Err(err) => { - eprintln!("ls: {}", err); - process::exit(1); - }, - }; - - let dot = File::from_name(BString::from("."), current.clone(), flags); - - let dot = match dot { - Ok(dot) => dot, - Err(err) => { - eprintln!("ls: {}", err); - process::exit(1); - }, - }; - - // Retrieve the parent path. Default to the current path if the - // parent doesn't exist - let parent_path = match dot.path.parent() { - Some(parent) => parent, - None => current.as_path(), - }; - - let dot_dot = - File::from_name(BString::from(".."), PathBuf::from(parent_path), flags); - - let dot_dot = match dot_dot { - Ok(dot_dot) => dot_dot, - Err(err) => { - eprintln!("ls: {}", err); - process::exit(1); - }, - }; - - result.insert(0, dot); - result.insert(1, dot_dot); - } + result = collect(file, &flags); } exit_code = output(result, &mut writer, flags); @@ -173,21 +108,88 @@ fn main() { } } -/// Collect the results from reading a directory into a `File` collection. -fn collect(dir: fs::ReadDir, flags: &Flags) -> io::Result { - let mut files = Files::new(); +fn collect(file: &str, flags: &Flags) -> Files { + let mut result = Files::new(); - for entry in dir { - let entry = entry?; + match fs::read_dir(file) { + Ok(dir) => { + for entry in dir { + let entry = match entry { + Ok(entry) => entry, + Err(err) => { + eprintln!("ls: cannot access '{}': {}", file, err); + process::exit(1); + }, + }; - let file = File::from(entry.path(), *flags)?; + let file = match File::from(entry.path(), *flags) { + Ok(file) => file, + Err(err) => { + eprintln!("ls: cannot access '{}': {}", file, err); + process::exit(1); + }, + }; - if !File::is_hidden(&file.name.as_bstr()) || flags.show_hidden() { - files.push(file); - } + if !File::is_hidden(&file.name.as_bstr()) || flags.show_hidden() { + result.push(file); + } + } + + if !flags.no_sort { + sort(&mut result, &flags); + } + }, + Err(err) => { + eprintln!("ls: cannot access '{}': {}", file, err); + process::exit(1); + }, + } + + if !flags.directory && (flags.all || flags.no_sort) { + // Retrieve the current directories information. This must + // be canonicalized in case the path is relative. + let current = PathBuf::from(file).canonicalize(); + + let current = match current { + Ok(current) => current, + Err(err) => { + eprintln!("ls: {}", err); + process::exit(1); + }, + }; + + let dot = File::from_name(BString::from("."), current.clone(), *flags); + + let dot = match dot { + Ok(dot) => dot, + Err(err) => { + eprintln!("ls: {}", err); + process::exit(1); + }, + }; + + // Retrieve the parent path. Default to the current path if the + // parent doesn't exist + let parent_path = match dot.path.parent() { + Some(parent) => parent, + None => current.as_path(), + }; + + let dot_dot = File::from_name(BString::from(".."), PathBuf::from(parent_path), *flags); + + let dot_dot = match dot_dot { + Ok(dot_dot) => dot_dot, + Err(err) => { + eprintln!("ls: {}", err); + process::exit(1); + }, + }; + + result.insert(0, dot); + result.insert(1, dot_dot); } - Ok(files) + result } /// Sort a list of files based on the provided flags. From 03f3ab57a62ce995792df3ae6a8e9e87213f0201 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Thu, 17 Dec 2020 16:42:41 -0600 Subject: [PATCH 126/133] Ls: display directories recursively --- ls/src/main.rs | 69 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 65 insertions(+), 4 deletions(-) diff --git a/ls/src/main.rs b/ls/src/main.rs index 4146adc8..e7def00b 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -54,7 +54,9 @@ fn main() { exit_code = output(result, &mut writer, flags); } else if flags.recursive { - todo!(); + for file in files { + exit_code = recursive_output(file, &mut writer, &flags); + } } else { let multiple = files.len() > 1; @@ -103,9 +105,7 @@ fn main() { } } - if exit_code != 0 { - process::exit(exit_code); - } + process::exit(exit_code); } fn collect(file: &str, flags: &Flags) -> Files { @@ -192,6 +192,67 @@ fn collect(file: &str, flags: &Flags) -> Files { result } +/// Recursively display sub directories from a given path. +fn recursive_output(file: &str, writer: &mut W, flags: &Flags) -> i32 { + match writeln!(writer, "\n{}:", file) { + Ok(_) => {}, + Err(err) => { + eprintln!("ls: '{}'", err); + }, + } + + let path = PathBuf::from(file); + + let files = if path.is_file() { + let mut result = Files::new(); + let item = File::from(PathBuf::from(file), *flags); + + match item { + Ok(item) => { + result.push(item); + }, + Err(err) => { + eprintln!("ls: cannot access {}: {}", err, file); + }, + }; + + result + } else { + collect(file, flags) + }; + let mut exit_code = output(files, writer, *flags); + + if path.is_file() { + return exit_code; + } + + match fs::read_dir(file) { + Ok(dir) => { + for entry in dir { + match entry { + Ok(entry) => { + let path = entry.path(); + + if path.is_dir() { + let file_string = path.to_string_lossy().to_string(); + + exit_code = recursive_output(&file_string, writer, &flags); + } + }, + Err(err) => { + eprintln!("ls: cannot access '{}': {}", file, err); + }, + }; + } + }, + Err(err) => { + eprintln!("ls: cannot access '{}': {}", file, err); + }, + } + + exit_code +} + /// Sort a list of files based on the provided flags. fn sort(files: &mut Files, flags: &Flags) { if flags.time { From 33bc2be257342e408fb8305668752ba394ffb673 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Sat, 26 Dec 2020 10:16:04 -0600 Subject: [PATCH 127/133] Ls: return io error --- ls/src/file.rs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/ls/src/file.rs b/ls/src/file.rs index 6b3c351e..93419e9a 100644 --- a/ls/src/file.rs +++ b/ls/src/file.rs @@ -5,23 +5,16 @@ use std::{ fs::{FileTypeExt, MetadataExt, PermissionsExt}, }, path::PathBuf, - result::Result, string::String, }; use coreutils_core::{ - os::{ - group::{Error as GroupError, Group}, - passwd::{Error as PasswdError, Passwd}, - tty::is_tty, - }, + os::{group::Group, passwd::Passwd, tty::is_tty}, BStr, BString, }; use ansi_term::Color; -extern crate chrono; - use chrono::{DateTime, Local, TimeZone}; use crate::flags::Flags; @@ -97,7 +90,7 @@ impl File { /// Retrieves the file's user name as a string. If the `-n` flag is set, /// the the user's ID is returned - pub fn user(&self) -> Result { + pub fn user(&self) -> io::Result { if self.flags.numeric_uid_gid { return Ok(BString::from(self.metadata.uid().to_string())); } @@ -110,7 +103,7 @@ impl File { /// Retrieves the file's group name as a string. If the `-n` flag is set, /// the the group's ID is returned - pub fn group(&self) -> Result { + pub fn group(&self) -> io::Result { if self.flags.numeric_uid_gid { return Ok(BString::from(self.metadata.gid().to_string())); } From 63740607445e46b2c8d0f372474b244693881b3b Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Sat, 26 Dec 2020 10:42:11 -0600 Subject: [PATCH 128/133] Ls: add comment --- ls/src/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/ls/src/main.rs b/ls/src/main.rs index e7def00b..e777c693 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -108,6 +108,7 @@ fn main() { process::exit(exit_code); } +/// Read the `&str` as a directory and collect the results into a `File` vector. fn collect(file: &str, flags: &Flags) -> Files { let mut result = Files::new(); From 9daf166ab36be52536ef5249c996bc6f50ce5ea4 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Sat, 26 Dec 2020 11:16:14 -0600 Subject: [PATCH 129/133] Ls: create new bufwriter --- ls/src/main.rs | 6 +++--- ls/src/output.rs | 12 +++++++----- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/ls/src/main.rs b/ls/src/main.rs index e777c693..5b5285cb 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -52,7 +52,7 @@ fn main() { sort(&mut result, &flags); - exit_code = output(result, &mut writer, flags); + exit_code = output(result, flags); } else if flags.recursive { for file in files { exit_code = recursive_output(file, &mut writer, &flags); @@ -101,7 +101,7 @@ fn main() { result = collect(file, &flags); } - exit_code = output(result, &mut writer, flags); + exit_code = output(result, flags); } } @@ -221,7 +221,7 @@ fn recursive_output(file: &str, writer: &mut W, flags: &Flags) -> i32 } else { collect(file, flags) }; - let mut exit_code = output(files, writer, *flags); + let mut exit_code = output(files, *flags); if path.is_file() { return exit_code; diff --git a/ls/src/output.rs b/ls/src/output.rs index 3fceb51e..173f00ca 100644 --- a/ls/src/output.rs +++ b/ls/src/output.rs @@ -1,5 +1,5 @@ use std::{ - io::{self, Write}, + io::{self, BufWriter, Write}, os::unix::fs::MetadataExt, }; @@ -18,11 +18,13 @@ use crate::{ table::{Row, Table}, }; -pub(crate) fn output(result: Files, writer: &mut W, flags: Flags) -> i32 { +pub(crate) fn output(result: Files, flags: Flags) -> i32 { let mut exit_code = 0; + let mut writer = BufWriter::new(io::stdout()); + if flags.show_list() { - match list(result, writer, flags) { + match list(result, &mut writer, flags) { Ok(_) => {}, Err(err) => { eprintln!("ls: {}", err); @@ -36,7 +38,7 @@ pub(crate) fn output(result: Files, writer: &mut W, flags: Flags) -> i Direction::TopToBottom }; - match grid(result, writer, direction) { + match grid(result, &mut writer, direction) { Ok(_) => {}, Err(err) => { eprintln!("ls: {}", err); @@ -44,7 +46,7 @@ pub(crate) fn output(result: Files, writer: &mut W, flags: Flags) -> i }, } } else { - match default(result, writer, flags) { + match default(result, &mut writer, flags) { Ok(_) => {}, Err(err) => { eprintln!("ls: {}", err); From 5cde34e0dae73409947fb11740115cb7b2d353a7 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Sat, 26 Dec 2020 11:31:07 -0600 Subject: [PATCH 130/133] Ls: add function comments --- ls/src/file.rs | 2 ++ ls/src/output.rs | 1 + 2 files changed, 3 insertions(+) diff --git a/ls/src/file.rs b/ls/src/file.rs index 93419e9a..ce30a55f 100644 --- a/ls/src/file.rs +++ b/ls/src/file.rs @@ -280,10 +280,12 @@ impl File { Color::Blue.bold().paint(directory_name.to_string()).to_string() } + /// Adds a yellow color on a black background to an FIFO name. pub fn add_fifo_color(&self, named_pipe_name: &BString) -> String { Color::Yellow.on(Color::Black).paint(named_pipe_name.to_string()).to_string() } + /// Adds a bold yellow color on a black background to a char device name. pub fn add_char_device_color(&self, char_device_name: &BString) -> String { Color::Yellow.on(Color::Black).bold().paint(char_device_name.to_string()).to_string() } diff --git a/ls/src/output.rs b/ls/src/output.rs index 173f00ca..b546dc41 100644 --- a/ls/src/output.rs +++ b/ls/src/output.rs @@ -18,6 +18,7 @@ use crate::{ table::{Row, Table}, }; +/// Outputs in the provided files in a style depending on the flags provided. pub(crate) fn output(result: Files, flags: Flags) -> i32 { let mut exit_code = 0; From fe448b5fdc405ed7133ddd5cb836da6fe3269c20 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Mon, 28 Dec 2020 08:09:35 -0600 Subject: [PATCH 131/133] Revert "Ls: create new bufwriter" This reverts commit 9daf166ab36be52536ef5249c996bc6f50ce5ea4. --- ls/src/main.rs | 6 +++--- ls/src/output.rs | 13 +++++-------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/ls/src/main.rs b/ls/src/main.rs index 5b5285cb..e777c693 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -52,7 +52,7 @@ fn main() { sort(&mut result, &flags); - exit_code = output(result, flags); + exit_code = output(result, &mut writer, flags); } else if flags.recursive { for file in files { exit_code = recursive_output(file, &mut writer, &flags); @@ -101,7 +101,7 @@ fn main() { result = collect(file, &flags); } - exit_code = output(result, flags); + exit_code = output(result, &mut writer, flags); } } @@ -221,7 +221,7 @@ fn recursive_output(file: &str, writer: &mut W, flags: &Flags) -> i32 } else { collect(file, flags) }; - let mut exit_code = output(files, *flags); + let mut exit_code = output(files, writer, *flags); if path.is_file() { return exit_code; diff --git a/ls/src/output.rs b/ls/src/output.rs index b546dc41..3fceb51e 100644 --- a/ls/src/output.rs +++ b/ls/src/output.rs @@ -1,5 +1,5 @@ use std::{ - io::{self, BufWriter, Write}, + io::{self, Write}, os::unix::fs::MetadataExt, }; @@ -18,14 +18,11 @@ use crate::{ table::{Row, Table}, }; -/// Outputs in the provided files in a style depending on the flags provided. -pub(crate) fn output(result: Files, flags: Flags) -> i32 { +pub(crate) fn output(result: Files, writer: &mut W, flags: Flags) -> i32 { let mut exit_code = 0; - let mut writer = BufWriter::new(io::stdout()); - if flags.show_list() { - match list(result, &mut writer, flags) { + match list(result, writer, flags) { Ok(_) => {}, Err(err) => { eprintln!("ls: {}", err); @@ -39,7 +36,7 @@ pub(crate) fn output(result: Files, flags: Flags) -> i32 { Direction::TopToBottom }; - match grid(result, &mut writer, direction) { + match grid(result, writer, direction) { Ok(_) => {}, Err(err) => { eprintln!("ls: {}", err); @@ -47,7 +44,7 @@ pub(crate) fn output(result: Files, flags: Flags) -> i32 { }, } } else { - match default(result, &mut writer, flags) { + match default(result, writer, flags) { Ok(_) => {}, Err(err) => { eprintln!("ls: {}", err); From e3c35c3f2aface5a11a90ec4b9f7c2e83d828691 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Mon, 28 Dec 2020 08:34:33 -0600 Subject: [PATCH 132/133] Ls: only exit with code if the code isn't zero --- ls/src/main.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ls/src/main.rs b/ls/src/main.rs index e777c693..6800796e 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -105,7 +105,9 @@ fn main() { } } - process::exit(exit_code); + if exit_code != 0 { + process::exit(exit_code); + } } /// Read the `&str` as a directory and collect the results into a `File` vector. From 9ffb259ab61c03fac5358eb10b6968ee158865ca Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Mon, 28 Dec 2020 12:03:51 -0600 Subject: [PATCH 133/133] Ls: check if bufwriter is a tty --- ls/src/main.rs | 2 +- ls/src/output.rs | 19 ++++++++++++------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/ls/src/main.rs b/ls/src/main.rs index 6800796e..7b4bec32 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -196,7 +196,7 @@ fn collect(file: &str, flags: &Flags) -> Files { } /// Recursively display sub directories from a given path. -fn recursive_output(file: &str, writer: &mut W, flags: &Flags) -> i32 { +fn recursive_output(file: &str, writer: &mut BufWriter, flags: &Flags) -> i32 { match writeln!(writer, "\n{}:", file) { Ok(_) => {}, Err(err) => { diff --git a/ls/src/output.rs b/ls/src/output.rs index 3fceb51e..6dfa7c41 100644 --- a/ls/src/output.rs +++ b/ls/src/output.rs @@ -1,13 +1,14 @@ use std::{ - io::{self, Write}, + io::{self, BufWriter, Write}, os::unix::fs::MetadataExt, }; use coreutils_core::{ - os::tty::{is_tty, tty_dimensions}, + os::tty::{tty_dimensions, IsTTY}, BString, ByteSlice, }; +use io::Stdout; use term_grid::{Alignment, Cell, Direction, Filling, Grid, GridOptions}; extern crate chrono; @@ -18,7 +19,7 @@ use crate::{ table::{Row, Table}, }; -pub(crate) fn output(result: Files, writer: &mut W, flags: Flags) -> i32 { +pub(crate) fn output(result: Files, writer: &mut BufWriter, flags: Flags) -> i32 { let mut exit_code = 0; if flags.show_list() { @@ -57,8 +58,10 @@ pub(crate) fn output(result: Files, writer: &mut W, flags: Flags) -> i } /// Writes the provided files in the default format. -pub(crate) fn default(files: Files, writer: &mut W, flags: Flags) -> io::Result<()> { - if !is_tty(&io::stdout()) { +pub(crate) fn default( + files: Files, writer: &mut BufWriter, flags: Flags, +) -> io::Result<()> { + if !writer.get_ref().is_tty() { for file in &files { if flags.hide_control_chars { writeln!(writer, "{}", file.name)?; @@ -95,7 +98,9 @@ pub(crate) fn default(files: Files, writer: &mut W, flags: Flags) -> i } /// Writes the provided files in a grid format. -pub(crate) fn grid(files: Files, writer: &mut W, direction: Direction) -> io::Result<()> { +pub(crate) fn grid( + files: Files, writer: &mut BufWriter, direction: Direction, +) -> io::Result<()> { let mut grid = Grid::new(GridOptions { filling: Filling::Spaces(2), direction }); let width = match tty_dimensions(&io::stdout()) { @@ -132,7 +137,7 @@ pub(crate) fn grid(files: Files, writer: &mut W, direction: Direction) } /// Writes the provided files in a list format. -pub(crate) fn list(files: Files, writer: &mut W, flags: Flags) -> io::Result<()> { +pub(crate) fn list(files: Files, writer: &mut BufWriter, flags: Flags) -> io::Result<()> { let mut inode_width = 1; let mut block_width = 1; let mut hard_links_width = 1;