diff options
author | Dom Slee <domslee1@gmail.com> | 2023-09-24 23:14:54 +1000 |
---|---|---|
committer | Wei Zhang <kweizh@gmail.com> | 2024-02-16 11:47:12 +0800 |
commit | 92d4e9662200b87bf54ddf97c37cf13d5f2ec9c4 (patch) | |
tree | 268632e6eed55e9e3a07c228948de96f0b780c58 /src | |
parent | 48b5adf9298338aaa67a1cf7a0c0ef00901388b0 (diff) |
Implementation
Diffstat (limited to 'src')
-rw-r--r-- | src/app.rs | 4 | ||||
-rw-r--r-- | src/color.rs | 17 | ||||
-rw-r--r-- | src/config_file.rs | 7 | ||||
-rw-r--r-- | src/core.rs | 24 | ||||
-rw-r--r-- | src/display.rs | 30 | ||||
-rw-r--r-- | src/flags/permission.rs | 23 | ||||
-rw-r--r-- | src/icon.rs | 24 | ||||
-rw-r--r-- | src/meta/filetype.rs | 13 | ||||
-rw-r--r-- | src/meta/mod.rs | 98 | ||||
-rw-r--r-- | src/meta/name.rs | 7 | ||||
-rw-r--r-- | src/meta/permissions.rs | 2 | ||||
-rw-r--r-- | src/meta/permissions_or_attributes.rs | 27 | ||||
-rw-r--r-- | src/meta/windows_attributes.rs | 126 | ||||
-rw-r--r-- | src/sort.rs | 74 | ||||
-rw-r--r-- | src/theme/color.rs | 27 |
15 files changed, 386 insertions, 117 deletions
@@ -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] diff --git a/src/color.rs b/src/color.rs index 8cb93c1..9a3a14b 100644 --- a/src/color.rs +++ b/src/color.rs @@ -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 diff --git a/src/config_file.rs b/src/config_file.rs index b69c040..9d35873 100644 --- a/src/config_file.rs +++ b/src/config_file.rs @@ -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), diff --git a/src/core.rs b/src/core.rs index 54ec086..d806ada 100644 --- a/src/core.rs +++ b/src/core.rs @@ -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)) diff --git a/src/display.rs b/src/display.rs index a6d4942..da4c897 100644 --- a/src/display.rs +++ b/src/display.rs @@ -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, diff --git a/src/flags/permission.rs b/src/flags/permission.rs index 2cced0b..bbaa562 100644 --- a/src/flags/permission.rs +++ b/src/flags/permission.rs @@ -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] @@ -100,6 +109,16 @@ mod test { } #[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"]; let cli = Cli::try_parse_from(argv).unwrap(); diff --git a/src/icon.rs b/src/icon.rs index d07eca2..6fec005 100644 --- a/src/icon.rs +++ b/src/icon.rs @@ -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); diff --git a/src/meta/filetype.rs b/src/meta/filetype.rs index 61f6348..173331d 100644 --- a/src/meta/filetype.rs +++ b/src/meta/filetype.rs @@ -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()); diff --git a/src/meta/mod.rs b/src/meta/mod.rs index 1f5d36d..1b0e4eb 100644 --- a/src/meta/mod.rs +++ b/src/meta/mod.rs @@ -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() ); } diff --git a/src/meta/name.rs b/src/meta/name.rs index 3b326c8..4d6ea6c 100644 --- a/src/meta/name.rs +++ b/src/meta/name.rs @@ -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); diff --git a/src/meta/permissions.rs b/src/meta/permissions.rs index af21a13..323675d 100644 --- a/src/meta/permissions.rs +++ b/src/meta/permissions.rs @@ -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(), }; diff --git a/src/meta/permissions_or_attributes.rs b/src/meta/permissions_or_attributes.rs new file mode 100644 index 0000000..d1c7347 --- /dev/null +++ b/src/meta/permissions_or_attributes.rs @@ -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) + } + } + } +} diff --git a/src/meta/windows_attributes.rs b/src/meta/windows_attributes.rs new file mode 100644 index 0000000..fa4a031 --- /dev/null +++ b/src/meta/windows_attributes.rs @@ -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) + } +}< |