mirror of
https://github.com/lsd-rs/lsd
synced 2024-12-14 06:02:36 +00:00
parent
c48f0f48f5
commit
f22ad5b2ef
6 changed files with 221 additions and 32 deletions
|
@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
[#712](https://github.com/Peltoche/lsd/issues/712). Executables will be marked
|
[#712](https://github.com/Peltoche/lsd/issues/712). Executables will be marked
|
||||||
based on the file extension: `exe`, `msi`, `bat` and `ps1`.
|
based on the file extension: `exe`, `msi`, `bat` and `ps1`.
|
||||||
[`LS_COLORS`](README.md#Colors) can be used to customize.
|
[`LS_COLORS`](README.md#Colors) can be used to customize.
|
||||||
|
- Handle dereference (-L) with broken symlink from [r3dArch](https://github.com/r3dArch)
|
||||||
|
|
||||||
## [0.23.1] - 2022-09-13
|
## [0.23.1] - 2022-09-13
|
||||||
|
|
||||||
|
|
|
@ -289,6 +289,8 @@ fn get_output(
|
||||||
tree: (usize, &str),
|
tree: (usize, &str),
|
||||||
) -> Vec<String> {
|
) -> Vec<String> {
|
||||||
let mut strings: Vec<String> = Vec::new();
|
let mut strings: Vec<String> = Vec::new();
|
||||||
|
let colorize_missing = |string: &str| colors.colorize(string, &Elem::NoAccess);
|
||||||
|
|
||||||
for (i, block) in flags.blocks.0.iter().enumerate() {
|
for (i, block) in flags.blocks.0.iter().enumerate() {
|
||||||
let mut block_vec = if Layout::Tree == flags.layout && tree.0 == i {
|
let mut block_vec = if Layout::Tree == flags.layout && tree.0 == i {
|
||||||
vec![colors.colorize(tree.1, &Elem::TreeEdge)]
|
vec![colors.colorize(tree.1, &Elem::TreeEdge)]
|
||||||
|
@ -297,28 +299,58 @@ fn get_output(
|
||||||
};
|
};
|
||||||
|
|
||||||
match block {
|
match block {
|
||||||
Block::INode => block_vec.push(meta.inode.render(colors)),
|
Block::INode => block_vec.push(match &meta.inode {
|
||||||
Block::Links => block_vec.push(meta.links.render(colors)),
|
Some(inode) => inode.render(colors),
|
||||||
|
None => colorize_missing("?"),
|
||||||
|
}),
|
||||||
|
Block::Links => block_vec.push(match &meta.links {
|
||||||
|
Some(links) => links.render(colors),
|
||||||
|
None => colorize_missing("?"),
|
||||||
|
}),
|
||||||
Block::Permission => {
|
Block::Permission => {
|
||||||
block_vec.extend([
|
block_vec.extend([
|
||||||
meta.file_type.render(colors),
|
meta.file_type.render(colors),
|
||||||
meta.permissions.render(colors, flags),
|
match meta.permissions {
|
||||||
meta.access_control.render_method(colors),
|
Some(permissions) => permissions.render(colors, flags),
|
||||||
|
None => colorize_missing("?????????"),
|
||||||
|
},
|
||||||
|
match &meta.access_control {
|
||||||
|
Some(access_control) => access_control.render_method(colors),
|
||||||
|
None => colorize_missing(""),
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
Block::User => block_vec.push(meta.owner.render_user(colors)),
|
Block::User => block_vec.push(match &meta.owner {
|
||||||
Block::Group => block_vec.push(meta.owner.render_group(colors)),
|
Some(owner) => owner.render_user(colors),
|
||||||
Block::Context => block_vec.push(meta.access_control.render_context(colors)),
|
None => colorize_missing("?"),
|
||||||
|
}),
|
||||||
|
Block::Group => block_vec.push(match &meta.owner {
|
||||||
|
Some(owner) => owner.render_group(colors),
|
||||||
|
None => colorize_missing("?"),
|
||||||
|
}),
|
||||||
|
Block::Context => block_vec.push(match &meta.access_control {
|
||||||
|
Some(access_control) => access_control.render_context(colors),
|
||||||
|
None => colorize_missing("?"),
|
||||||
|
}),
|
||||||
Block::Size => {
|
Block::Size => {
|
||||||
let pad = if Layout::Tree == flags.layout && 0 == tree.0 && 0 == i {
|
let pad = if Layout::Tree == flags.layout && 0 == tree.0 && 0 == i {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(padding_rules[&Block::SizeValue])
|
Some(padding_rules[&Block::SizeValue])
|
||||||
};
|
};
|
||||||
block_vec.push(meta.size.render(colors, flags, pad))
|
block_vec.push(match &meta.size {
|
||||||
|
Some(size) => size.render(colors, flags, pad),
|
||||||
|
None => colorize_missing("?"),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
Block::SizeValue => block_vec.push(meta.size.render_value(colors, flags)),
|
Block::SizeValue => block_vec.push(match &meta.size {
|
||||||
Block::Date => block_vec.push(meta.date.render(colors, flags)),
|
Some(size) => size.render_value(colors, flags),
|
||||||
|
None => colorize_missing("?"),
|
||||||
|
}),
|
||||||
|
Block::Date => block_vec.push(match &meta.date {
|
||||||
|
Some(date) => date.render(colors, flags),
|
||||||
|
None => colorize_missing("?"),
|
||||||
|
}),
|
||||||
Block::Name => {
|
Block::Name => {
|
||||||
block_vec.extend([
|
block_vec.extend([
|
||||||
meta.name.render(
|
meta.name.render(
|
||||||
|
@ -377,7 +409,10 @@ fn detect_size_lengths(metas: &[Meta], flags: &Flags) -> usize {
|
||||||
let mut max_value_length: usize = 0;
|
let mut max_value_length: usize = 0;
|
||||||
|
|
||||||
for meta in metas {
|
for meta in metas {
|
||||||
let value_len = meta.size.value_string(flags).len();
|
let value_len = match &meta.size {
|
||||||
|
Some(size) => size.value_string(flags).len(),
|
||||||
|
None => 0,
|
||||||
|
};
|
||||||
|
|
||||||
if value_len > max_value_length {
|
if value_len > max_value_length {
|
||||||
max_value_length = value_len;
|
max_value_length = value_len;
|
||||||
|
|
|
@ -149,8 +149,9 @@ mod test {
|
||||||
let metadata = tmp_dir.path().metadata().expect("failed to get metas");
|
let metadata = tmp_dir.path().metadata().expect("failed to get metas");
|
||||||
|
|
||||||
let colors = Colors::new(ThemeOption::NoLscolors);
|
let colors = Colors::new(ThemeOption::NoLscolors);
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
let file_type = FileType::new(&metadata, None, &meta.permissions);
|
let file_type = FileType::new(&metadata, None, &meta.permissions.unwrap());
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
let file_type = FileType::new(&metadata, None, tmp_dir.path());
|
let file_type = FileType::new(&metadata, None, tmp_dir.path());
|
||||||
|
|
||||||
|
|
105
src/meta/mod.rs
105
src/meta/mod.rs
|
@ -36,17 +36,17 @@ use std::path::{Component, Path, PathBuf};
|
||||||
pub struct Meta {
|
pub struct Meta {
|
||||||
pub name: Name,
|
pub name: Name,
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
pub permissions: Permissions,
|
pub permissions: Option<Permissions>,
|
||||||
pub date: Date,
|
pub date: Option<Date>,
|
||||||
pub owner: Owner,
|
pub owner: Option<Owner>,
|
||||||
pub file_type: FileType,
|
pub file_type: FileType,
|
||||||
pub size: Size,
|
pub size: Option<Size>,
|
||||||
pub symlink: SymLink,
|
pub symlink: SymLink,
|
||||||
pub indicator: Indicator,
|
pub indicator: Indicator,
|
||||||
pub inode: INode,
|
pub inode: Option<INode>,
|
||||||
pub links: Links,
|
pub links: Option<Links>,
|
||||||
pub content: Option<Vec<Meta>>,
|
pub content: Option<Vec<Meta>>,
|
||||||
pub access_control: AccessControl,
|
pub access_control: Option<AccessControl>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Meta {
|
impl Meta {
|
||||||
|
@ -169,17 +169,27 @@ impl Meta {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn calculate_total_size(&mut self) {
|
pub fn calculate_total_size(&mut self) {
|
||||||
|
if self.size.is_none() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if let FileType::Directory { .. } = self.file_type {
|
if let FileType::Directory { .. } = self.file_type {
|
||||||
if let Some(metas) = &mut self.content {
|
if let Some(metas) = &mut self.content {
|
||||||
let mut size_accumulated = self.size.get_bytes();
|
let mut size_accumulated = match &self.size {
|
||||||
|
Some(size) => size.get_bytes(),
|
||||||
|
None => 0,
|
||||||
|
};
|
||||||
for x in &mut metas.iter_mut() {
|
for x in &mut metas.iter_mut() {
|
||||||
x.calculate_total_size();
|
x.calculate_total_size();
|
||||||
size_accumulated += x.size.get_bytes();
|
size_accumulated += match &x.size {
|
||||||
|
Some(size) => size.get_bytes(),
|
||||||
|
None => 0,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
self.size = Size::new(size_accumulated);
|
self.size = Some(Size::new(size_accumulated));
|
||||||
} else {
|
} else {
|
||||||
// possibility that 'depth' limited the recursion in 'recurse_into'
|
// possibility that 'depth' limited the recursion in 'recurse_into'
|
||||||
self.size = Size::new(Meta::calculate_total_file_size(&self.path));
|
self.size = Some(Size::new(Meta::calculate_total_file_size(&self.path)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -225,6 +235,7 @@ impl Meta {
|
||||||
pub fn from_path(path: &Path, dereference: bool) -> io::Result<Self> {
|
pub fn from_path(path: &Path, dereference: bool) -> io::Result<Self> {
|
||||||
let mut metadata = path.symlink_metadata()?;
|
let mut metadata = path.symlink_metadata()?;
|
||||||
let mut symlink_meta = None;
|
let mut symlink_meta = None;
|
||||||
|
let mut broken_link = false;
|
||||||
if metadata.file_type().is_symlink() {
|
if metadata.file_type().is_symlink() {
|
||||||
match path.metadata() {
|
match path.metadata() {
|
||||||
Ok(m) => {
|
Ok(m) => {
|
||||||
|
@ -238,7 +249,8 @@ impl Meta {
|
||||||
// This case, it is definitely a symlink or
|
// This case, it is definitely a symlink or
|
||||||
// path.symlink_metadata would have errored out
|
// path.symlink_metadata would have errored out
|
||||||
if dereference {
|
if dereference {
|
||||||
return Err(e);
|
broken_link = true;
|
||||||
|
eprintln!("lsd: {}: {}\n", path.to_str().unwrap_or(""), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -252,8 +264,6 @@ impl Meta {
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
let (owner, permissions) = windows_utils::get_file_data(path)?;
|
let (owner, permissions) = windows_utils::get_file_data(path)?;
|
||||||
|
|
||||||
let access_control = AccessControl::for_path(path);
|
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
let file_type = FileType::new(&metadata, symlink_meta.as_ref(), &permissions);
|
let file_type = FileType::new(&metadata, symlink_meta.as_ref(), &permissions);
|
||||||
|
|
||||||
|
@ -261,16 +271,27 @@ impl Meta {
|
||||||
let file_type = FileType::new(&metadata, symlink_meta.as_ref(), path);
|
let file_type = FileType::new(&metadata, symlink_meta.as_ref(), path);
|
||||||
|
|
||||||
let name = Name::new(path, file_type);
|
let name = Name::new(path, file_type);
|
||||||
let inode = INode::from(&metadata);
|
|
||||||
let links = Links::from(&metadata);
|
let (inode, links, size, date, owner, permissions, access_control) = match broken_link {
|
||||||
|
true => (None, None, None, None, None, None, None),
|
||||||
|
false => (
|
||||||
|
Some(INode::from(&metadata)),
|
||||||
|
Some(Links::from(&metadata)),
|
||||||
|
Some(Size::from(&metadata)),
|
||||||
|
Some(Date::from(&metadata)),
|
||||||
|
Some(owner),
|
||||||
|
Some(permissions),
|
||||||
|
Some(AccessControl::for_path(path)),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
inode,
|
inode,
|
||||||
links,
|
links,
|
||||||
path: path.to_path_buf(),
|
path: path.to_path_buf(),
|
||||||
symlink: SymLink::from(path),
|
symlink: SymLink::from(path),
|
||||||
size: Size::from(&metadata),
|
size,
|
||||||
date: Date::from(&metadata),
|
date,
|
||||||
indicator: Indicator::from(file_type),
|
indicator: Indicator::from(file_type),
|
||||||
owner,
|
owner,
|
||||||
permissions,
|
permissions,
|
||||||
|
@ -282,15 +303,61 @@ impl Meta {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Meta;
|
use super::Meta;
|
||||||
|
use std::fs::File;
|
||||||
|
use tempfile::tempdir;
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
#[test]
|
#[test]
|
||||||
fn test_from_path_path() {
|
fn test_from_path_path() {
|
||||||
let dir = assert_fs::TempDir::new().unwrap();
|
let dir = assert_fs::TempDir::new().unwrap();
|
||||||
let meta = Meta::from_path(dir.path(), false).unwrap();
|
let meta = Meta::from_path(dir.path(), false).unwrap();
|
||||||
assert_eq!(meta.path, dir.path())
|
assert_eq!(meta.path, dir.path())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_from_path() {
|
||||||
|
let tmp_dir = tempdir().expect("failed to create temp dir");
|
||||||
|
|
||||||
|
let path_a = tmp_dir.path().join("aaa.aa");
|
||||||
|
File::create(&path_a).expect("failed to create file");
|
||||||
|
let meta_a = Meta::from_path(&path_a, false).expect("failed to get meta");
|
||||||
|
|
||||||
|
let path_b = tmp_dir.path().join("bbb.bb");
|
||||||
|
let path_c = tmp_dir.path().join("ccc.cc");
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
std::os::unix::fs::symlink(&path_c, &path_b).expect("failed to create broken symlink");
|
||||||
|
|
||||||
|
// this needs to be tested on Windows
|
||||||
|
// likely to fail because of permission issue
|
||||||
|
// see https://doc.rust-lang.org/std/os/windows/fs/fn.symlink_file.html
|
||||||
|
#[cfg(windows)]
|
||||||
|
std::os::windows::fs::symlink_file(&path_c, &path_b)
|
||||||
|
.expect("failed to create broken symlink");
|
||||||
|
|
||||||
|
let meta_b = Meta::from_path(&path_b, true).expect("failed to get meta");
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
meta_a.inode.is_some()
|
||||||
|
&& meta_a.links.is_some()
|
||||||
|
&& meta_a.size.is_some()
|
||||||
|
&& meta_a.date.is_some()
|
||||||
|
&& meta_a.owner.is_some()
|
||||||
|
&& meta_a.permissions.is_some()
|
||||||
|
&& meta_a.access_control.is_some()
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
meta_b.inode.is_none()
|
||||||
|
&& meta_b.links.is_none()
|
||||||
|
&& meta_b.size.is_none()
|
||||||
|
&& meta_b.date.is_none()
|
||||||
|
&& meta_b.owner.is_none()
|
||||||
|
&& meta_b.permissions.is_none()
|
||||||
|
&& meta_b.access_control.is_none()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
54
src/sort.rs
54
src/sort.rs
|
@ -48,7 +48,12 @@ fn with_dirs_first(a: &Meta, b: &Meta) -> Ordering {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn by_size(a: &Meta, b: &Meta) -> Ordering {
|
fn by_size(a: &Meta, b: &Meta) -> Ordering {
|
||||||
b.size.get_bytes().cmp(&a.size.get_bytes())
|
match (&a.size, &b.size) {
|
||||||
|
(Some(a_size), Some(b_size)) => b_size.get_bytes().cmp(&a_size.get_bytes()),
|
||||||
|
(Some(_), None) => Ordering::Greater,
|
||||||
|
(None, Some(_)) => Ordering::Less,
|
||||||
|
(None, None) => Ordering::Equal,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn by_name(a: &Meta, b: &Meta) -> Ordering {
|
fn by_name(a: &Meta, b: &Meta) -> Ordering {
|
||||||
|
@ -72,6 +77,7 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::flags::Flags;
|
use crate::flags::Flags;
|
||||||
use std::fs::{create_dir, File};
|
use std::fs::{create_dir, File};
|
||||||
|
use std::io::prelude::*;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use tempfile::tempdir;
|
use tempfile::tempdir;
|
||||||
|
|
||||||
|
@ -338,4 +344,50 @@ mod tests {
|
||||||
let sorter = assemble_sorters(&flags);
|
let sorter = assemble_sorters(&flags);
|
||||||
assert_eq!(by_meta(&sorter, &meta_c, &meta_d), Ordering::Equal);
|
assert_eq!(by_meta(&sorter, &meta_c, &meta_d), Ordering::Equal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sort_by_size() {
|
||||||
|
let tmp_dir = tempdir().expect("failed to create temp dir");
|
||||||
|
|
||||||
|
let path_a = tmp_dir.path().join("aaa.aa");
|
||||||
|
File::create(&path_a)
|
||||||
|
.expect("failed to create file")
|
||||||
|
.write_all(b"1, 2, 3")
|
||||||
|
.expect("failed to write to file");
|
||||||
|
let meta_a = Meta::from_path(&path_a, false).expect("failed to get meta");
|
||||||
|
|
||||||
|
let path_b = tmp_dir.path().join("bbb.bb");
|
||||||
|
File::create(&path_b)
|
||||||
|
.expect("failed to create file")
|
||||||
|
.write_all(b"1, 2, 3, 4, 5, 6, 7, 8, 9, 10")
|
||||||
|
.expect("failed to write file");
|
||||||
|
let meta_b = Meta::from_path(&path_b, false).expect("failed to get meta");
|
||||||
|
|
||||||
|
let path_c = tmp_dir.path().join("ccc.cc");
|
||||||
|
let path_d = tmp_dir.path().join("ddd.dd");
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
std::os::unix::fs::symlink(&path_d, &path_c).expect("failed to create broken symlink");
|
||||||
|
|
||||||
|
// this needs to be tested on Windows
|
||||||
|
// likely to fail because of permission issue
|
||||||
|
// see https://doc.rust-lang.org/std/os/windows/fs/fn.symlink_file.html
|
||||||
|
#[cfg(windows)]
|
||||||
|
std::os::windows::fs::symlink_file(&path_d, &path_c)
|
||||||
|
.expect("failed to create broken symlink");
|
||||||
|
|
||||||
|
let meta_c = Meta::from_path(&path_c, true).expect("failed to get meta");
|
||||||
|
|
||||||
|
assert_eq!(by_size(&meta_a, &meta_a), Ordering::Equal);
|
||||||
|
assert_eq!(by_size(&meta_a, &meta_b), Ordering::Greater);
|
||||||
|
assert_eq!(by_size(&meta_a, &meta_c), Ordering::Greater);
|
||||||
|
|
||||||
|
assert_eq!(by_size(&meta_b, &meta_a), Ordering::Less);
|
||||||
|
assert_eq!(by_size(&meta_b, &meta_b), Ordering::Equal);
|
||||||
|
assert_eq!(by_size(&meta_b, &meta_c), Ordering::Greater);
|
||||||
|
|
||||||
|
assert_eq!(by_size(&meta_c, &meta_a), Ordering::Less);
|
||||||
|
assert_eq!(by_size(&meta_c, &meta_b), Ordering::Less);
|
||||||
|
assert_eq!(by_size(&meta_c, &meta_c), Ordering::Equal);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -289,6 +289,39 @@ fn test_dereference_link_broken_link() {
|
||||||
.stderr(predicate::str::contains("No such file or directory"));
|
.stderr(predicate::str::contains("No such file or directory"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_dereference_link_broken_link_output() {
|
||||||
|
let dir = tempdir();
|
||||||
|
|
||||||
|
let link = dir.path().join("link");
|
||||||
|
let target = dir.path().join("target");
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
fs::symlink(&target, &link).unwrap();
|
||||||
|
|
||||||
|
// this needs to be tested on Windows
|
||||||
|
// likely to fail because of permission issue
|
||||||
|
// see https://doc.rust-lang.org/std/os/windows/fs/fn.symlink_file.html
|
||||||
|
#[cfg(windows)]
|
||||||
|
std::os::windows::fs::symlink_file(&target, &link).expect("failed to create broken symlink");
|
||||||
|
|
||||||
|
cmd()
|
||||||
|
.arg("-l")
|
||||||
|
.arg("--dereference")
|
||||||
|
.arg("--ignore-config")
|
||||||
|
.arg(&link)
|
||||||
|
.assert()
|
||||||
|
.stdout(predicate::str::starts_with("l????????? ? ? ? ?"));
|
||||||
|
|
||||||
|
cmd()
|
||||||
|
.arg("-l")
|
||||||
|
.arg("-L")
|
||||||
|
.arg("--ignore-config")
|
||||||
|
.arg(link)
|
||||||
|
.assert()
|
||||||
|
.stdout(predicate::str::starts_with("l????????? ? ? ? ?"));
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
#[test]
|
#[test]
|
||||||
fn test_show_folder_content_of_symlink() {
|
fn test_show_folder_content_of_symlink() {
|
||||||
|
|
Loading…
Reference in a new issue