Implementation

This commit is contained in:
Dom Slee 2023-09-24 23:14:54 +10:00 committed by Wei Zhang
parent 48b5adf929
commit 92d4e96622
15 changed files with 386 additions and 117 deletions

View file

@ -68,8 +68,8 @@ pub struct Cli {
#[arg(short, long, conflicts_with_all = ["depth", "recursive"])]
pub directory_only: bool,
/// How to display permissions [default: rwx]
#[arg(long, value_name = "MODE", value_parser = ["rwx", "octal", "disable"])]
/// How to display permissions [default: rwx for non-windows, attributes for windows]
#[arg(long, value_name = "MODE", value_parser = ["rwx", "octal", "attributes", "disable"])]
pub permission: Option<String>,
/// How to display size [default: default]

View file

@ -38,6 +38,12 @@ pub enum Elem {
Acl,
Context,
/// Attributes
Archive,
AttributeRead,
Hidden,
System,
/// Last Time Modified
DayOld,
HourOld,
@ -112,6 +118,11 @@ impl Elem {
Elem::Acl => theme.permission.acl,
Elem::Context => theme.permission.context,
Elem::Archive => theme.attributes.archive,
Elem::AttributeRead => theme.attributes.read,
Elem::Hidden => theme.attributes.hidden,
Elem::System => theme.attributes.system,
Elem::DayOld => theme.date.day_old,
Elem::HourOld => theme.date.hour_old,
Elem::Older => theme.date.older,
@ -399,6 +410,12 @@ mod elem {
acl: Color::DarkCyan,
context: Color::Cyan,
},
attributes: color::Attributes {
read: Color::Green,
archive: Color::Yellow,
hidden: Color::Red,
system: Color::Magenta,
},
file_type: color::FileType {
file: color::File {
exec_uid: Color::AnsiValue(40), // Green3

View file

@ -299,8 +299,8 @@ size: default
# == Permission ==
# Specify the format of the permission column.
# Possible value: rwx, octal
permission: rwx
# Possible value: rwx, octal, attributes, disable
# permission: rwx
# == Sorting ==
sorting:
@ -363,7 +363,6 @@ mod tests {
use crate::flags::color::{ColorOption, ThemeOption};
use crate::flags::icons::{IconOption, IconTheme};
use crate::flags::layout::Layout;
use crate::flags::permission::PermissionFlag;
use crate::flags::size::SizeFlag;
use crate::flags::sorting::{DirGrouping, SortColumn};
use crate::flags::HyperlinkOption;
@ -402,7 +401,7 @@ mod tests {
depth: None,
}),
size: Some(SizeFlag::Default),
permission: Some(PermissionFlag::Rwx),
permission: None,
sorting: Some(config_file::Sorting {
column: Some(SortColumn::Name),
reverse: Some(false),

View file

@ -1,8 +1,7 @@
use crate::color::Colors;
use crate::display;
use crate::flags::{
ColorOption, Display, Flags, HyperlinkOption, Layout, Literal, PermissionFlag, SortOrder,
ThemeOption,
ColorOption, Display, Flags, HyperlinkOption, Layout, Literal, SortOrder, ThemeOption,
};
use crate::git::GitCache;
use crate::icon::Icons;
@ -106,18 +105,15 @@ impl Core {
};
for path in paths {
let mut meta = match Meta::from_path(
&path,
self.flags.dereference.0,
self.flags.permission == PermissionFlag::Disable,
) {
Ok(meta) => meta,
Err(err) => {
print_error!("{}: {}.", path.display(), err);
exit_code.set_if_greater(ExitCode::MajorIssue);
continue;
}
};
let mut meta =
match Meta::from_path(&path, self.flags.dereference.0, self.flags.permission) {
Ok(meta) => meta,
Err(err) => {
print_error!("{}: {}.", path.display(), err);
exit_code.set_if_greater(ExitCode::MajorIssue);
continue;
}
};
let cache = if self.flags.blocks.0.contains(&Block::GitStatus) {
Some(GitCache::new(&path))

View file

@ -347,8 +347,10 @@ fn get_output(
Block::Permission => {
block_vec.extend([
meta.file_type.render(colors),
match meta.permissions {
Some(permissions) => permissions.render(colors, flags),
match &meta.permissions_or_attributes {
Some(permissions_or_attributes) => {
permissions_or_attributes.render(colors, flags)
}
None => colorize_missing("?????????"),
},
match &meta.access_control {
@ -491,7 +493,7 @@ mod tests {
use crate::app::Cli;
use crate::color;
use crate::color::Colors;
use crate::flags::{HyperlinkOption, IconOption, IconTheme as FlagTheme};
use crate::flags::{HyperlinkOption, IconOption, IconTheme as FlagTheme, PermissionFlag};
use crate::icon::Icons;
use crate::meta::{FileType, Name};
use crate::Config;
@ -686,7 +688,7 @@ mod tests {
dir.child("one.d").create_dir_all().unwrap();
dir.child("one.d/two").touch().unwrap();
dir.child("one.d/.hidden").touch().unwrap();
let mut metas = Meta::from_path(Path::new(dir.path()), false, false)
let mut metas = Meta::from_path(Path::new(dir.path()), false, PermissionFlag::Rwx)
.unwrap()
.recurse_into(42, &flags, None)
.unwrap()
@ -719,7 +721,7 @@ mod tests {
let dir = assert_fs::TempDir::new().unwrap();
dir.child("dir").create_dir_all().unwrap();
dir.child("dir/file").touch().unwrap();
let metas = Meta::from_path(Path::new(dir.path()), false, false)
let metas = Meta::from_path(Path::new(dir.path()), false, PermissionFlag::Rwx)
.unwrap()
.recurse_into(42, &flags, None)
.unwrap()
@ -760,7 +762,7 @@ mod tests {
let dir = assert_fs::TempDir::new().unwrap();
dir.child("dir").create_dir_all().unwrap();
dir.child("dir/file").touch().unwrap();
let metas = Meta::from_path(Path::new(dir.path()), false, false)
let metas = Meta::from_path(Path::new(dir.path()), false, PermissionFlag::Rwx)
.unwrap()
.recurse_into(42, &flags, None)
.unwrap()
@ -800,7 +802,7 @@ mod tests {
let dir = assert_fs::TempDir::new().unwrap();
dir.child("one.d").create_dir_all().unwrap();
dir.child("one.d/two").touch().unwrap();
let metas = Meta::from_path(Path::new(dir.path()), false, false)
let metas = Meta::from_path(Path::new(dir.path()), false, PermissionFlag::Rwx)
.unwrap()
.recurse_into(42, &flags, None)
.unwrap()
@ -831,7 +833,7 @@ mod tests {
let dir = assert_fs::TempDir::new().unwrap();
dir.child("testdir").create_dir_all().unwrap();
dir.child("test").touch().unwrap();
let metas = Meta::from_path(Path::new(dir.path()), false, false)
let metas = Meta::from_path(Path::new(dir.path()), false, PermissionFlag::Rwx)
.unwrap()
.recurse_into(1, &flags, None)
.unwrap()
@ -865,7 +867,7 @@ mod tests {
let dir = assert_fs::TempDir::new().unwrap();
dir.child("testdir").create_dir_all().unwrap();
let metas = Meta::from_path(Path::new(dir.path()), false, false)
let metas = Meta::from_path(Path::new(dir.path()), false, PermissionFlag::Rwx)
.unwrap()
.recurse_into(1, &flags, None)
.unwrap()
@ -895,11 +897,11 @@ mod tests {
let file_path = tmp_dir.path().join("file");
std::fs::File::create(&file_path).expect("failed to create the file");
let file = Meta::from_path(&file_path, false, false).unwrap();
let file = Meta::from_path(&file_path, false, PermissionFlag::Rwx).unwrap();
let dir_path = tmp_dir.path().join("dir");
std::fs::create_dir(&dir_path).expect("failed to create the dir");
let dir = Meta::from_path(&dir_path, false, false).unwrap();
let dir = Meta::from_path(&dir_path, false, PermissionFlag::Rwx).unwrap();
assert_eq!(
display_folder_path(&dir),
@ -945,15 +947,15 @@ mod tests {
let file_path = tmp_dir.path().join("file");
std::fs::File::create(&file_path).expect("failed to create the file");
let file = Meta::from_path(&file_path, false, false).unwrap();
let file = Meta::from_path(&file_path, false, PermissionFlag::Rwx).unwrap();
let dir_path = tmp_dir.path().join("dir");
std::fs::create_dir(&dir_path).expect("failed to create the dir");
let dir = Meta::from_path(&dir_path, false, false).unwrap();
let dir = Meta::from_path(&dir_path, false, PermissionFlag::Rwx).unwrap();
let link_path = tmp_dir.path().join("link");
std::os::unix::fs::symlink("dir", &link_path).unwrap();
let link = Meta::from_path(&link_path, false, false).unwrap();
let link = Meta::from_path(&link_path, false, PermissionFlag::Rwx).unwrap();
let grid_flags = Flags {
layout: Layout::Grid,

View file

@ -13,10 +13,13 @@ use serde::Deserialize;
#[serde(rename_all = "kebab-case")]
pub enum PermissionFlag {
/// The variant to show file permissions in rwx format
#[default]
#[cfg_attr(not(target_os = "windows"), default)]
Rwx,
/// The variant to show file permissions in octal format
Octal,
/// (windows only): Attributes from powershell's `Get-ChildItem`
#[cfg_attr(target_os = "windows", default)]
Attributes,
/// Disable the display of owner and permissions, may be used to speed up in Windows
Disable,
}
@ -26,6 +29,7 @@ impl PermissionFlag {
match value {
"rwx" => Self::Rwx,
"octal" => Self::Octal,
"attributes" => Self::Attributes,
"disable" => Self::Disable,
// Invalid value should be handled by `clap` when building an `Cli`
other => unreachable!("Invalid value '{other}' for 'permission'"),
@ -75,7 +79,12 @@ mod test {
#[test]
fn test_default() {
assert_eq!(PermissionFlag::Rwx, PermissionFlag::default());
let expected = if cfg!(target_os = "windows") {
PermissionFlag::Attributes
} else {
PermissionFlag::Rwx
};
assert_eq!(expected, PermissionFlag::default());
}
#[test]
@ -99,6 +108,16 @@ mod test {
assert_eq!(Some(PermissionFlag::Octal), PermissionFlag::from_cli(&cli));
}
#[test]
fn test_from_cli_attributes() {
let argv = ["lsd", "--permission", "attributes"];
let cli = Cli::try_parse_from(argv).unwrap();
assert_eq!(
Some(PermissionFlag::Attributes),
PermissionFlag::from_cli(&cli)
);
}
#[test]
fn test_from_cli_permissions_disable() {
let argv = ["lsd", "--permission", "disable"];

View file

@ -75,7 +75,7 @@ impl Icons {
#[cfg(test)]
mod test {
use super::{IconTheme, Icons};
use crate::flags::{IconOption, IconTheme as FlagTheme};
use crate::flags::{IconOption, IconTheme as FlagTheme, PermissionFlag};
use crate::meta::Meta;
use std::fs::File;
use tempfile::tempdir;
@ -85,7 +85,7 @@ mod test {
let tmp_dir = tempdir().expect("failed to create temp dir");
let file_path = tmp_dir.path().join("file.txt");
File::create(&file_path).expect("failed to create file");
let meta = Meta::from_path(&file_path, false, false).unwrap();
let meta = Meta::from_path(&file_path, false, PermissionFlag::Rwx).unwrap();
let icons = Icons::new(true, IconOption::Never, FlagTheme::Fancy, " ".to_string());
let icon = icons.get(&meta.name);
@ -97,7 +97,7 @@ mod test {
let tmp_dir = tempdir().expect("failed to create temp dir");
let file_path = tmp_dir.path().join("file.txt");
File::create(&file_path).expect("failed to create file");
let meta = Meta::from_path(&file_path, false, false).unwrap();
let meta = Meta::from_path(&file_path, false, PermissionFlag::Rwx).unwrap();
let icons = Icons::new(false, IconOption::Never, FlagTheme::Fancy, " ".to_string());
let icon = icons.get(&meta.name);
@ -110,7 +110,7 @@ mod test {
let tmp_dir = tempdir().expect("failed to create temp dir");
let file_path = tmp_dir.path().join("file.txt");
File::create(&file_path).expect("failed to create file");
let meta = Meta::from_path(&file_path, false, false).unwrap();
let meta = Meta::from_path(&file_path, false, PermissionFlag::Rwx).unwrap();
let icons = Icons::new(false, IconOption::Auto, FlagTheme::Fancy, " ".to_string());
let icon = icons.get(&meta.name);
@ -122,7 +122,7 @@ mod test {
let tmp_dir = tempdir().expect("failed to create temp dir");
let file_path = tmp_dir.path().join("file.txt");
File::create(&file_path).expect("failed to create file");
let meta = Meta::from_path(&file_path, false, false).unwrap();
let meta = Meta::from_path(&file_path, false, PermissionFlag::Rwx).unwrap();
let icons = Icons::new(true, IconOption::Auto, FlagTheme::Fancy, " ".to_string());
let icon = icons.get(&meta.name);
@ -135,7 +135,7 @@ mod test {
let tmp_dir = tempdir().expect("failed to create temp dir");
let file_path = tmp_dir.path().join("file");
File::create(&file_path).expect("failed to create file");
let meta = Meta::from_path(&file_path, false, false).unwrap();
let meta = Meta::from_path(&file_path, false, PermissionFlag::Rwx).unwrap();
let icon = Icons::new(true, IconOption::Always, FlagTheme::Fancy, " ".to_string());
let icon_str = icon.get(&meta.name);
@ -148,7 +148,7 @@ mod test {
let tmp_dir = tempdir().expect("failed to create temp dir");
let file_path = tmp_dir.path().join("file");
File::create(&file_path).expect("failed to create file");
let meta = Meta::from_path(&file_path, false, false).unwrap();
let meta = Meta::from_path(&file_path, false, PermissionFlag::Rwx).unwrap();
let icon = Icons::new(false, IconOption::Always, FlagTheme::Fancy, " ".to_string());
let icon_str = icon.get(&meta.name);
@ -161,7 +161,7 @@ mod test {
let tmp_dir = tempdir().expect("failed to create temp dir");
let file_path = tmp_dir.path().join("file");
File::create(&file_path).expect("failed to create file");
let meta = Meta::from_path(&file_path, false, false).unwrap();
let meta = Meta::from_path(&file_path, false, PermissionFlag::Rwx).unwrap();
let icon = Icons::new(
false,
@ -178,7 +178,7 @@ mod test {
fn get_icon_default_directory() {
let tmp_dir = tempdir().expect("failed to create temp dir");
let file_path = tmp_dir.path();
let meta = Meta::from_path(file_path, false, false).unwrap();
let meta = Meta::from_path(file_path, false, PermissionFlag::Rwx).unwrap();
let icon = Icons::new(false, IconOption::Always, FlagTheme::Fancy, " ".to_string());
let icon_str = icon.get(&meta.name);
@ -190,7 +190,7 @@ mod test {
fn get_icon_default_directory_unicode() {
let tmp_dir = tempdir().expect("failed to create temp dir");
let file_path = tmp_dir.path();
let meta = Meta::from_path(file_path, false, false).unwrap();
let meta = Meta::from_path(file_path, false, PermissionFlag::Rwx).unwrap();
let icon = Icons::new(
false,
@ -210,7 +210,7 @@ mod test {
for (file_name, file_icon) in &IconTheme::get_default_icons_by_name() {
let file_path = tmp_dir.path().join(file_name);
File::create(&file_path).expect("failed to create file");
let meta = Meta::from_path(&file_path, false, false).unwrap();
let meta = Meta::from_path(&file_path, false, PermissionFlag::Rwx).unwrap();
let icon = Icons::new(false, IconOption::Always, FlagTheme::Fancy, " ".to_string());
let icon_str = icon.get(&meta.name);
@ -226,7 +226,7 @@ mod test {
for (ext, file_icon) in &IconTheme::get_default_icons_by_extension() {
let file_path = tmp_dir.path().join(format!("file.{ext}"));
File::create(&file_path).expect("failed to create file");
let meta = Meta::from_path(&file_path, false, false).unwrap();
let meta = Meta::from_path(&file_path, false, PermissionFlag::Rwx).unwrap();
let icon = Icons::new(false, IconOption::Always, FlagTheme::Fancy, " ".to_string());
let icon_str = icon.get(&meta.name);

View file

@ -110,6 +110,10 @@ mod test {
use super::FileType;
use crate::color::{Colors, ThemeOption};
#[cfg(unix)]
use crate::flags::PermissionFlag;
#[cfg(unix)]
use crate::meta::permissions_or_attributes::PermissionsOrAttributes;
#[cfg(unix)]
use crate::meta::Permissions;
use crossterm::style::{Color, Stylize};
use std::fs::File;
@ -144,14 +148,19 @@ mod test {
fn test_dir_type() {
let tmp_dir = tempdir().expect("failed to create temp dir");
#[cfg(not(windows))]
let meta = crate::meta::Meta::from_path(tmp_dir.path(), false, false)
let meta = crate::meta::Meta::from_path(tmp_dir.path(), false, PermissionFlag::Rwx)
.expect("failed to get tempdir path");
let metadata = tmp_dir.path().metadata().expect("failed to get metas");
let colors = Colors::new(ThemeOption::NoLscolors);
#[cfg(not(windows))]
let file_type = FileType::new(&metadata, None, &meta.permissions.unwrap());
let file_type = match meta.permissions_or_attributes {
Some(PermissionsOrAttributes::Permissions(permissions)) => {
FileType::new(&metadata, None, &permissions)
}
_ => panic!("unexpected"),
};
#[cfg(windows)]
let file_type = FileType::new(&metadata, None, tmp_dir.path());

View file

@ -9,8 +9,10 @@ mod locale;
pub mod name;
mod owner;
mod permissions;
mod permissions_or_attributes;
mod size;
mod symlink;
mod windows_attributes;
#[cfg(windows)]
mod windows_utils;
@ -25,6 +27,7 @@ pub use self::links::Links;
pub use self::name::Name;
pub use self::owner::Owner;
pub use self::permissions::Permissions;
use self::permissions_or_attributes::PermissionsOrAttributes;
pub use self::size::Size;
pub use self::symlink::SymLink;
@ -35,11 +38,13 @@ use crate::git::GitCache;
use std::io::{self, Error, ErrorKind};
use std::path::{Component, Path, PathBuf};
#[cfg(windows)]
use self::windows_attributes::get_attributes;
#[derive(Clone, Debug)]
pub struct Meta {
pub name: Name,
pub path: PathBuf,
pub permissions: Option<Permissions>,
pub permissions_or_attributes: Option<PermissionsOrAttributes>,
pub date: Option<Date>,
pub owner: Option<Owner>,
pub file_type: FileType,
@ -97,7 +102,7 @@ impl Meta {
let mut parent_meta = Self::from_path(
&self.path.join(Component::ParentDir),
flags.dereference.0,
flags.permission == PermissionFlag::Disable,
flags.permission,
)?;
parent_meta.name.name = "..".to_owned();
@ -141,11 +146,8 @@ impl Meta {
_ => {}
}
let mut entry_meta = match Self::from_path(
&path,
flags.dereference.0,
flags.permission == PermissionFlag::Disable,
) {
let mut entry_meta = match Self::from_path(&path, flags.dereference.0, flags.permission)
{
Ok(res) => res,
Err(err) => {
print_error!("{}: {}.", path.display(), err);
@ -250,7 +252,11 @@ impl Meta {
}
}
pub fn from_path(path: &Path, dereference: bool, disable_permission: bool) -> io::Result<Self> {
pub fn from_path(
path: &Path,
dereference: bool,
permission_flag: PermissionFlag,
) -> io::Result<Self> {
let mut metadata = path.symlink_metadata()?;
let mut symlink_meta = None;
let mut broken_link = false;
@ -275,21 +281,30 @@ impl Meta {
}
#[cfg(unix)]
let (owner, permissions) = if disable_permission {
(None, None)
} else {
(
let (owner, permissions) = match permission_flag {
PermissionFlag::Disable => (None, None),
_ => (
Some(Owner::from(&metadata)),
Some(Permissions::from(&metadata)),
)
),
};
#[cfg(unix)]
let permissions_or_attributes = permissions.map(PermissionsOrAttributes::Permissions);
#[cfg(windows)]
let (owner, permissions) = if disable_permission {
(None, None)
} else {
match windows_utils::get_file_data(path) {
Ok((owner, permissions)) => (Some(owner), Some(permissions)),
let (owner, permissions_or_attributes) = match permission_flag {
PermissionFlag::Disable => (None, None),
PermissionFlag::Attributes => (
None,
Some(PermissionsOrAttributes::WindowsAttributes(get_attributes(
&metadata,
))),
),
_ => match windows_utils::get_file_data(path) {
Ok((owner, permissions)) => (
Some(owner),
Some(PermissionsOrAttributes::Permissions(permissions)),
),
Err(e) => {
eprintln!(
"lsd: {}: {}(Hint: Consider using `--permission disabled`.)",
@ -298,7 +313,7 @@ impl Meta {
);
(None, None)
}
}
},
};
#[cfg(not(windows))]
@ -313,18 +328,19 @@ impl Meta {
let name = Name::new(path, file_type);
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)),
),
};
let (inode, links, size, date, owner, permissions_or_attributes, 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_or_attributes),
Some(AccessControl::for_path(path)),
),
};
Ok(Self {
inode,
@ -335,7 +351,7 @@ impl Meta {
date,
indicator: Indicator::from(file_type),
owner: owner.unwrap_or_default(),
permissions: permissions.unwrap_or_default(),
permissions_or_attributes: permissions_or_attributes.unwrap_or_default(),
name,
file_type,
content: None,
@ -347,6 +363,8 @@ impl Meta {
#[cfg(test)]
mod tests {
use crate::flags::PermissionFlag;
use super::Meta;
use std::fs::File;
use tempfile::tempdir;
@ -355,15 +373,15 @@ mod tests {
#[test]
fn test_from_path_path() {
let dir = assert_fs::TempDir::new().unwrap();
let meta = Meta::from_path(dir.path(), false, false).unwrap();
let meta = Meta::from_path(dir.path(), false, PermissionFlag::Rwx).unwrap();
assert_eq!(meta.path, dir.path())
}
#[test]
fn test_from_path_disable_permission() {
let dir = assert_fs::TempDir::new().unwrap();
let meta = Meta::from_path(dir.path(), false, true).unwrap();
assert!(meta.permissions.is_none());
let meta = Meta::from_path(dir.path(), false, PermissionFlag::Disable).unwrap();
assert!(meta.permissions_or_attributes.is_none());
assert!(meta.owner.is_none());
}
@ -373,7 +391,8 @@ mod tests {
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, false).expect("failed to get meta");
let meta_a =
Meta::from_path(&path_a, false, PermissionFlag::Rwx).expect("failed to get meta");
let path_b = tmp_dir.path().join("bbb.bb");
let path_c = tmp_dir.path().join("ccc.cc");
@ -388,7 +407,8 @@ mod tests {
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, false).expect("failed to get meta");
let meta_b =
Meta::from_path(&path_b, true, PermissionFlag::Rwx).expect("failed to get meta");
assert!(
meta_a.inode.is_some()
@ -396,7 +416,7 @@ mod tests {
&& meta_a.size.is_some()
&& meta_a.date.is_some()
&& meta_a.owner.is_some()
&& meta_a.permissions.is_some()
&& meta_a.permissions_or_attributes.is_some()
&& meta_a.access_control.is_some()
);
@ -406,7 +426,7 @@ mod tests {
&& meta_b.size.is_none()
&& meta_b.date.is_none()
&& meta_b.owner.is_none()
&& meta_b.permissions.is_none()
&& meta_b.permissions_or_attributes.is_none()
&& meta_b.access_control.is_none()
);
}

View file

@ -222,6 +222,7 @@ mod test {
use super::DisplayOption;
use super::Name;
use crate::color::{self, Colors};
use crate::flags::PermissionFlag;
use crate::flags::{HyperlinkOption, IconOption, IconTheme as FlagTheme};
use crate::icon::Icons;
use crate::meta::FileType;
@ -274,7 +275,7 @@ mod test {
// Create the directory
let dir_path = tmp_dir.path().join("directory");
fs::create_dir(&dir_path).expect("failed to create the dir");
let meta = Meta::from_path(&dir_path, false, false).unwrap();
let meta = Meta::from_path(&dir_path, false, PermissionFlag::Rwx).unwrap();
let colors = Colors::new(color::ThemeOption::NoLscolors);
@ -398,7 +399,7 @@ mod test {
// Create the file;
let file_path = tmp_dir.path().join("file.txt");
File::create(&file_path).expect("failed to create file");
let meta = Meta::from_path(&file_path, false, false).unwrap();
let meta = Meta::from_path(&file_path, false, PermissionFlag::Rwx).unwrap();
let colors = Colors::new(color::ThemeOption::NoColor);
@ -424,7 +425,7 @@ mod test {
// Create the file;
let file_path = tmp_dir.path().join("file.txt");
File::create(&file_path).expect("failed to create file");
let meta = Meta::from_path(&file_path, false, false).unwrap();
let meta = Meta::from_path(&file_path, false, PermissionFlag::Rwx).unwrap();
let colors = Colors::new(color::ThemeOption::NoColor);

View file

@ -122,6 +122,8 @@ impl Permissions {
colors.colorize(octals, &Elem::Octal).to_string()
}
// technically this should be an error, hmm
PermissionFlag::Attributes => colors.colorize('-', &Elem::NoAccess).to_string(),
PermissionFlag::Disable => colors.colorize('-', &Elem::NoAccess).to_string(),
};

View file

@ -0,0 +1,27 @@
#[cfg(windows)]
use super::windows_attributes::WindowsAttributes;
use crate::{
color::{ColoredString, Colors},
flags::Flags,
};
use super::Permissions;
#[derive(Clone, Debug)]
pub enum PermissionsOrAttributes {
Permissions(Permissions),
#[cfg(windows)]
WindowsAttributes(WindowsAttributes),
}
impl PermissionsOrAttributes {
pub fn render(&self, colors: &Colors, flags: &Flags) -> ColoredString {
match self {
PermissionsOrAttributes::Permissions(permissions) => permissions.render(colors, flags),
#[cfg(windows)]
PermissionsOrAttributes::WindowsAttributes(attributes) => {
attributes.render(colors, flags)
}
}
}
}

View file

@ -0,0 +1,126 @@
#[cfg(windows)]
use crate::{
color::{ColoredString, Colors, Elem},
flags::Flags,
};
#[cfg(windows)]
use std::os::windows::fs::MetadataExt;
#[cfg(windows)]
#[derive(Debug, Clone)]
pub struct WindowsAttributes {
pub archive: bool,
pub readonly: bool,
pub hidden: bool,
pub system: bool,
}
#[cfg(windows)]
pub fn get_attributes(metadata: &std::fs::Metadata) -> WindowsAttributes {
use windows::Win32::Storage::FileSystem::{
FILE_ATTRIBUTE_ARCHIVE, FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_READONLY,
FILE_ATTRIBUTE_SYSTEM, FILE_FLAGS_AND_ATTRIBUTES,
};
let bits = metadata.file_attributes();
let has_bit = |bit: FILE_FLAGS_AND_ATTRIBUTES| bits & bit.0 == bit.0;
// https://docs.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants
WindowsAttributes {
archive: has_bit(FILE_ATTRIBUTE_ARCHIVE),
readonly: has_bit(FILE_ATTRIBUTE_READONLY),
hidden: has_bit(FILE_ATTRIBUTE_HIDDEN),
system: has_bit(FILE_ATTRIBUTE_SYSTEM),
}
}
#[cfg(windows)]
impl WindowsAttributes {
pub fn render(&self, colors: &Colors, _flags: &Flags) -> ColoredString {
let res = [
match self.archive {
true => colors.colorize("a", &Elem::Archive),
false => colors.colorize('-', &Elem::NoAccess),
},
match self.readonly {
true => colors.colorize("r", &Elem::AttributeRead),
false => colors.colorize('-', &Elem::NoAccess),
},
match self.hidden {
true => colors.colorize("h", &Elem::Hidden),
false => colors.colorize('-', &Elem::NoAccess),
},
match self.system {
true => colors.colorize("s", &Elem::System),
false => colors.colorize('-', &Elem::NoAccess),
},
]
.into_iter()
.fold(String::with_capacity(4), |mut acc, x| {
acc.push_str(&x.to_string());
acc
});
ColoredString::new(Colors::default_style(), res)
}
}
#[cfg(windows)]
#[cfg(test)]
mod test {
use std::fs;
use std::io::Write;
use std::process::Command;
use crate::{
color::{Colors, ThemeOption},
flags::Flags,
};
use super::get_attributes;
use tempfile::tempdir;
#[test]
pub fn archived_file() {
let attribute_string = create_and_process_file_with_attributes("archived_file.txt", "+A");
assert_eq!("a---", attribute_string);
}
#[test]
pub fn readonly_file() {
let attribute_string = create_and_process_file_with_attributes("readonly_file.txt", "+R");
assert_eq!("ar--", attribute_string);
}
#[test]
pub fn hidden_file() {
let attribute_string = create_and_process_file_with_attributes("hidden_file.txt", "+H");
assert_eq!("a-h-", attribute_string);
}
#[test]
pub fn system_file() {
let attribute_string = create_and_process_file_with_attributes("system_file.txt", "+S");
assert_eq!("a--s", attribute_string);
}
fn create_and_process_file_with_attributes(name: &str, attrs: &str) -> String {
let tmp_dir = tempdir().expect("failed to create temp dir");
let path = tmp_dir.path().join(name);
let mut file = fs::File::create(path.clone()).unwrap();
writeln!(file, "Test content").unwrap();
Command::new("attrib")
.arg(attrs)
.arg(&path)
.output()
.expect("able to set attributes");
let metadata = file.metadata().expect("able to get metadata");
let colors = Colors::new(ThemeOption::NoColor);
let attributes = get_attributes(&metadata);
attributes
.render(&colors, &Flags::default())
.content()
.to_string()
}
}

View file

@ -80,7 +80,7 @@ fn by_git_status(a: &Meta, b: &Meta) -> Ordering {
#[cfg(test)]
mod tests {
use super::*;
use crate::flags::Flags;
use crate::flags::{Flags, PermissionFlag};
use std::fs::{create_dir, File};
use std::io::prelude::*;
use std::process::Command;
@ -93,12 +93,14 @@ mod tests {
// Create the file;
let path_a = tmp_dir.path().join("zzz");
File::create(&path_a).expect("failed to create file");
let meta_a = Meta::from_path(&path_a, false, false).expect("failed to get meta");
let meta_a =
Meta::from_path(&path_a, false, PermissionFlag::Rwx).expect("failed to get meta");
// Create a dir;
let path_z = tmp_dir.path().join("aaa");
create_dir(&path_z).expect("failed to create dir");
let meta_z = Meta::from_path(&path_z, false, false).expect("failed to get meta");
let meta_z =
Meta::from_path(&path_z, false, PermissionFlag::Rwx).expect("failed to get meta");
let mut flags = Flags::default();
flags.sorting.dir_grouping = DirGrouping::First;
@ -121,12 +123,14 @@ mod tests {
// Create the file;
let path_a = tmp_dir.path().join("zzz");
File::create(&path_a).expect("failed to create file");
let meta_a = Meta::from_path(&path_a, false, false).expect("failed to get meta");
let meta_a =
Meta::from_path(&path_a, false, PermissionFlag::Rwx).expect("failed to get meta");
// Create a dir;
let path_z = tmp_dir.path().join("aaa");
create_dir(&path_z).expect("failed to create dir");
let meta_z = Meta::from_path(&path_z, false, false).expect("failed to get meta");
let meta_z =
Meta::from_path(&path_z, false, PermissionFlag::Rwx).expect("failed to get meta");
let mut flags = Flags::default();
flags.sorting.dir_grouping = DirGrouping::Last;
@ -147,12 +151,14 @@ mod tests {
// Create the file;
let path_a = tmp_dir.path().join("aaa");
File::create(&path_a).expect("failed to create file");
let meta_a = Meta::from_path(&path_a, false, false).expect("failed to get meta");
let meta_a =
Meta::from_path(&path_a, false, PermissionFlag::Rwx).expect("failed to get meta");
// Create a dir;
let path_z = tmp_dir.path().join("zzz");
create_dir(&path_z).expect("failed to create dir");
let meta_z = Meta::from_path(&path_z, false, false).expect("failed to get meta");
let meta_z =
Meta::from_path(&path_z, false, PermissionFlag::Rwx).expect("failed to get meta");
let mut flags = Flags::default();
flags.sorting.dir_grouping = DirGrouping::None;
@ -175,12 +181,14 @@ mod tests {
// Create the file;
let path_a = tmp_dir.path().join("zzz");
File::create(&path_a).expect("failed to create file");
let meta_a = Meta::from_path(&path_a, false, false).expect("failed to get meta");
let meta_a =
Meta::from_path(&path_a, false, PermissionFlag::Rwx).expect("failed to get meta");
// Create a dir;
let path_z = tmp_dir.path().join("aaa");
create_dir(&path_z).expect("failed to create dir");
let meta_z = Meta::from_path(&path_z, false, false).expect("failed to get meta");
let meta_z =
Meta::from_path(&path_z, false, PermissionFlag::Rwx).expect("failed to get meta");
let mut flags = Flags::default();
flags.sorting.dir_grouping = DirGrouping::None;
@ -203,7 +211,8 @@ mod tests {
// Create the file;
let path_a = tmp_dir.path().join("aaa");
File::create(&path_a).expect("failed to create file");
let meta_a = Meta::from_path(&path_a, false, false).expect("failed to get meta");
let meta_a =
Meta::from_path(&path_a, false, PermissionFlag::Rwx).expect("failed to get meta");
// Create the file;
let path_z = tmp_dir.path().join("zzz");
@ -229,7 +238,8 @@ mod tests {
.success();
assert!(success, "failed to change file timestamp");
let meta_z = Meta::from_path(&path_z, false, false).expect("failed to get meta");
let meta_z =
Meta::from_path(&path_z, false, PermissionFlag::Rwx).expect("failed to get meta");
let mut flags = Flags::default();
flags.sorting.column = SortColumn::Time;
@ -251,22 +261,26 @@ mod tests {
// Create the file with rs extension;
let path_a = tmp_dir.path().join("aaa.rs");
File::create(&path_a).expect("failed to create file");
let meta_a = Meta::from_path(&path_a, false, false).expect("failed to get meta");
let meta_a =
Meta::from_path(&path_a, false, PermissionFlag::Rwx).expect("failed to get meta");
// Create the file with rs extension;
let path_z = tmp_dir.path().join("zzz.rs");
File::create(&path_z).expect("failed to create file");
let meta_z = Meta::from_path(&path_z, false, false).expect("failed to get meta");
let meta_z =
Meta::from_path(&path_z, false, PermissionFlag::Rwx).expect("failed to get meta");
// Create the file with js extension;
let path_j = tmp_dir.path().join("zzz.js");
File::create(&path_j).expect("failed to create file");
let meta_j = Meta::from_path(&path_j, false, false).expect("failed to get meta");
let meta_j =
Meta::from_path(&path_j, false, PermissionFlag::Rwx).expect("failed to get meta");
// Create the file with txt extension;
let path_t = tmp_dir.path().join("zzz.txt");
File::create(&path_t).expect("failed to create file");
let meta_t = Meta::from_path(&path_t, false, false).expect("failed to get meta");
let meta_t =
Meta::from_path(&path_t, false, PermissionFlag::Rwx).expect("failed to get meta");
let mut flags = Flags::default();
flags.sorting.column = SortColumn::Extension;
@ -288,15 +302,18 @@ mod tests {
let path_a = tmp_dir.path().join("2");
File::create(&path_a).expect("failed to create file");
let meta_a = Meta::from_path(&path_a, false, false).expect("failed to get meta");
let meta_a =
Meta::from_path(&path_a, false, PermissionFlag::Rwx).expect("failed to get meta");
let path_b = tmp_dir.path().join("11");
File::create(&path_b).expect("failed to create file");
let meta_b = Meta::from_path(&path_b, false, false).expect("failed to get meta");
let meta_b =
Meta::from_path(&path_b, false, PermissionFlag::Rwx).expect("failed to get meta");
let path_c = tmp_dir.path().join("12");
File::create(&path_c).expect("failed to create file");
let meta_c = Meta::from_path(&path_c, false, false).expect("failed to get meta");
let meta_c =
Meta::from_path(&path_c, false, PermissionFlag::Rwx).expect("failed to get meta");
let mut flags = Flags::default();
flags.sorting.column = SortColumn::Version;
@ -314,19 +331,23 @@ mod tests {
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, false).expect("failed to get meta");
let meta_a =
Meta::from_path(&path_a, false, PermissionFlag::Rwx).expect("failed to get meta");
let path_b = tmp_dir.path().join("aaa");
create_dir(&path_b).expect("failed to create dir");
let meta_b = Meta::from_path(&path_b, false, false).expect("failed to get meta");
let meta_b =
Meta::from_path(&path_b, false, PermissionFlag::Rwx).expect("failed to get meta");
let path_c = tmp_dir.path().join("zzz.zz");
File::create(&path_c).expect("failed to create file");
let meta_c = Meta::from_path(&path_c, false, false).expect("failed to get meta");
let meta_c =
Meta::from_path(&path_c, false, PermissionFlag::Rwx).expect("failed to get meta");
let path_d = tmp_dir.path().join("zzz");
create_dir(&path_d).expect("failed to create dir");
let meta_d = Meta::from_path(&path_d, false, false).expect("failed to get meta");
let meta_d =
Meta::from_path(&path_d, false, PermissionFlag::Rwx).expect("failed to get meta");
let mut flags = Flags::default();
flags.sorting.column = SortColumn::None;
@ -359,14 +380,16 @@ mod tests {
.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, false).expect("failed to get meta");
let meta_a =
Meta::from_path(&path_a, false, PermissionFlag::Rwx).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, false).expect("failed to get meta");
let meta_b =
Meta::from_path(&path_b, false, PermissionFlag::Rwx).expect("failed to get meta");
let path_c = tmp_dir.path().join("ccc.cc");
let path_d = tmp_dir.path().join("ddd.dd");
@ -381,7 +404,8 @@ mod tests {
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, false).expect("failed to get meta");
let meta_c =
Meta::from_path(&path_c, true, PermissionFlag::Rwx).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);

View file

@ -89,6 +89,7 @@ pub struct ColorTheme {
#[serde(deserialize_with = "deserialize_color")]
pub group: Color,
pub permission: Permission,
pub attributes: Attributes,
pub date: Date,
pub size: Size,
pub inode: INode,
@ -124,6 +125,21 @@ pub struct Permission {
pub context: Color,
}
#[derive(Debug, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
#[serde(deny_unknown_fields)]
#[serde(default)]
pub struct Attributes {
#[serde(deserialize_with = "deserialize_color")]
pub archive: Color,
#[serde(deserialize_with = "deserialize_color")]
pub read: Color,
#[serde(deserialize_with = "deserialize_color")]
pub hidden: Color,
#[serde(deserialize_with = "deserialize_color")]
pub system: Color,
}
#[derive(Debug, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
#[serde(deny_unknown_fields)]
@ -274,6 +290,16 @@ impl Default for Permission {
}
}
}
impl Default for Attributes {
fn default() -> Self {
Attributes {
archive: Color::DarkGreen,
read: Color::DarkYellow,
hidden: Color::AnsiValue(13), // Pink,
system: Color::AnsiValue(13), // Pink,
}
}
}
impl Default for FileType {
fn default() -> Self {
FileType {
@ -381,6 +407,7 @@ impl ColorTheme {
user: Color::AnsiValue(230), // Cornsilk1
group: Color::AnsiValue(187), // LightYellow3
permission: Permission::default(),
attributes: Attributes::default(),
file_type: FileType::default(),
date: Date::default(),
size: Size::default(),