feat(flags): Add Windows file attributes

This commit is contained in:
Robert Minsk 2023-12-04 17:30:34 -08:00 committed by Christina Sørensen
parent 458a6f5b1e
commit 66d16a9c9a
10 changed files with 197 additions and 8 deletions

View file

@ -201,7 +201,7 @@ These options are available when running with `--long` (`-l`):
: List numeric user and group IDs.
`-O`, `--flags`
: List file flags. See chflags(1) for a list of file flags and their meanings. (Mac and BSD only)
: List file flags on Mac and BSD systems and file attributes on Windows systems. By default, Windows attributes are displayed in a long form. To display in attributes as single character set the environment variable `EZA_WINDOWS_ATTRIBUTES=short`. On BSD systems see chflags(1) for a list of file flags and their meanings.
`-S`, `--blocksize`
: List each files size of allocated file system blocks.

View file

@ -898,12 +898,18 @@ impl<'dir> File<'dir> {
f::Flags(self.metadata.st_flags())
}
#[cfg(windows)]
pub fn flags(&self) -> f::Flags {
f::Flags(self.metadata.file_attributes())
}
#[cfg(not(any(
target_os = "macos",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
target_os = "dragonfly"
target_os = "dragonfly",
target_os = "windows"
)))]
pub fn flags(&self) -> f::Flags {
f::Flags(0)

View file

@ -61,7 +61,7 @@ LONG VIEW OPTIONS
-m, --modified use the modified timestamp field
-M, --mounts show mount details (Linux and Mac only)
-n, --numeric list numeric user and group IDs
-O, --flags list file flags (Mac and BSD only)
-O, --flags list file flags (Mac, BSD, and Windows only)
-S, --blocksize show size of allocated file system blocks
-t, --time FIELD which timestamp field to list (modified, accessed, created)
-u, --accessed use the accessed timestamp field

View file

@ -66,6 +66,11 @@ pub static EZA_ICONS_AUTO: &str = "EZA_ICONS_AUTO";
pub static EZA_STDIN_SEPARATOR: &str = "EZA_STDIN_SEPARATOR";
/// Environment variable used to choose how windows attributes are displayed.
/// Short will display a single character for each set attribute, long will
/// display a comma separated list of descriptions.
pub static EZA_WINDOWS_ATTRIBUTES: &str = "EZA_WINDOWS_ATTRIBUTES";
/// Mockable wrapper for `std::env::var_os`.
pub trait Vars {
fn get(&self, name: &'static str) -> Option<OsString>;

View file

@ -7,7 +7,7 @@ use crate::output::color_scale::{ColorScaleMode, ColorScaleOptions};
use crate::output::file_name::Options as FileStyle;
use crate::output::grid_details::{self, RowThreshold};
use crate::output::table::{
Columns, GroupFormat, Options as TableOptions, SizeFormat, TimeTypes, UserFormat,
Columns, FlagsFormat, GroupFormat, Options as TableOptions, SizeFormat, TimeTypes, UserFormat,
};
use crate::output::time::TimeFormat;
use crate::output::{details, grid, Mode, TerminalWidth, View};
@ -237,12 +237,14 @@ impl TableOptions {
let size_format = SizeFormat::deduce(matches)?;
let user_format = UserFormat::deduce(matches)?;
let group_format = GroupFormat::deduce(matches)?;
let flags_format = FlagsFormat::deduce(vars);
let columns = Columns::deduce(matches, vars)?;
Ok(Self {
size_format,
time_format,
user_format,
group_format,
flags_format,
columns,
})
}

View file

@ -2,9 +2,10 @@ use ansiterm::Style;
use crate::fs::fields as f;
use crate::output::cell::TextCell;
use crate::output::table::FlagsFormat;
impl f::Flags {
pub fn render(self, style: Style) -> TextCell {
pub fn render(self, style: Style, _format: FlagsFormat) -> TextCell {
TextCell::paint(style, "-".to_string())
}
}

View file

@ -3,6 +3,7 @@ use std::ffi::CStr;
use crate::fs::fields as f;
use crate::output::cell::TextCell;
use crate::output::table::FlagsFormat;
extern "C" {
fn fflagstostr(flags: libc::c_ulong) -> *const libc::c_char;
@ -33,7 +34,7 @@ fn flags_to_string(flags: f::flag_t) -> String {
}
impl f::Flags {
pub fn render(self, style: Style) -> TextCell {
pub fn render(self, style: Style, _format: FlagsFormat) -> TextCell {
TextCell::paint(style, flags_to_string(self.0))
}
}

View file

@ -0,0 +1,138 @@
use crate::fs::fields as f;
use crate::output::table::FlagsFormat;
use crate::output::TextCell;
use ansiterm::Style;
// See https://learn.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants
const FILE_ATTRIBUTE_READONLY: u32 = 0x0000_0001; // R
const FILE_ATTRIBUTE_HIDDEN: u32 = 0x0000_0002; // H
const FILE_ATTRIBUTE_SYSTEM: u32 = 0x0000_0004; // S
const FILE_ATTRIBUTE_ARCHIVE: u32 = 0x0000_0020; // A
const FILE_ATTRIBUTE_TEMPORARY: u32 = 0x0000_0100; // T
const FILE_ATTRIBUTE_COMPRESSED: u32 = 0x0000_0800; // C
const FILE_ATTRIBUTE_OFFLINE: u32 = 0x0000_1000; // O
const FILE_ATTRIBUTE_NOT_CONTENT_INDEXED: u32 = 0x0000_2000; // I
const FILE_ATTRIBUTE_ENCRYPTED: u32 = 0x0000_4000; // E
const FILE_ATTRIBUTE_NO_SCRUB_DATA: u32 = 0x0002_0000; // X
const FILE_ATTRIBUTE_PINNED: u32 = 0x0008_0000; // P
const FILE_ATTRIBUTE_UNPINNED: u32 = 0x0010_0000; // U
const FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS: u32 = 0x0040_0000; // M
struct Attribute {
flag: u32,
name: &'static str,
abbr: char,
}
const ATTRIBUTES: [Attribute; 13] = [
Attribute {
flag: FILE_ATTRIBUTE_READONLY,
name: "readonly",
abbr: 'R',
},
Attribute {
flag: FILE_ATTRIBUTE_HIDDEN,
name: "hidden",
abbr: 'H',
},
Attribute {
flag: FILE_ATTRIBUTE_SYSTEM,
name: "system",
abbr: 'S',
},
Attribute {
flag: FILE_ATTRIBUTE_ARCHIVE,
name: "archive",
abbr: 'A',
},
Attribute {
flag: FILE_ATTRIBUTE_TEMPORARY,
name: "temporary",
abbr: 'T',
},
Attribute {
flag: FILE_ATTRIBUTE_COMPRESSED,
name: "compressed",
abbr: 'C',
},
Attribute {
flag: FILE_ATTRIBUTE_OFFLINE,
name: "offline",
abbr: 'O',
},
Attribute {
flag: FILE_ATTRIBUTE_NOT_CONTENT_INDEXED,
name: "not indexed",
abbr: 'I',
},
Attribute {
flag: FILE_ATTRIBUTE_ENCRYPTED,
name: "encrypted",
abbr: 'E',
},
Attribute {
flag: FILE_ATTRIBUTE_NO_SCRUB_DATA,
name: "no scrub",
abbr: 'X',
},
Attribute {
flag: FILE_ATTRIBUTE_UNPINNED,
name: "unpinned",
abbr: 'U',
},
Attribute {
flag: FILE_ATTRIBUTE_PINNED,
name: "pinned",
abbr: 'P',
},
Attribute {
flag: FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS,
name: "recall on data access",
abbr: 'M',
},
];
fn flags_to_bsd_string(flags: f::flag_t) -> String {
let mut result = Vec::new();
for attribute in &ATTRIBUTES {
if attribute.flag & flags != 0 {
result.push(attribute.name);
}
}
if result.is_empty() {
"-".to_string()
} else {
result.join("-")
}
}
fn flags_to_windows_string(flags: f::flag_t) -> String {
let mut result = String::new();
for attribute in &ATTRIBUTES {
if attribute.flag & flags != 0 {
result.push(attribute.abbr);
}
}
if result.is_empty() {
result.push('-');
}
result
}
impl f::Flags {
pub fn render(self, style: Style, format: FlagsFormat) -> TextCell {
TextCell::paint(
style,
if format == FlagsFormat::Short {
flags_to_windows_string(self.0)
} else {
flags_to_bsd_string(self.0)
},
)
}
}

View file

@ -55,11 +55,15 @@ pub use self::securityctx::Colours as SecurityCtxColours;
))]
mod flags_bsd;
#[cfg(windows)]
mod flags_windows;
#[cfg(not(any(
target_os = "macos",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
target_os = "dragonfly"
target_os = "dragonfly",
target_os = "windows"
)))]
mod flags;

View file

@ -12,6 +12,8 @@ use uzers::UsersCache;
use crate::fs::feature::git::GitCache;
use crate::fs::{fields as f, File};
use crate::options::vars::EZA_WINDOWS_ATTRIBUTES;
use crate::options::Vars;
use crate::output::cell::TextCell;
use crate::output::color_scale::ColorScaleInformation;
#[cfg(unix)]
@ -29,6 +31,7 @@ pub struct Options {
pub time_format: TimeFormat,
pub user_format: UserFormat,
pub group_format: GroupFormat,
pub flags_format: FlagsFormat,
pub columns: Columns,
}
@ -304,6 +307,33 @@ impl TimeType {
}
}
/// How display file flags.
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
pub enum FlagsFormat {
/// Display flags as comma seperated descriptions
Long,
/// Display flags as single character abbreviations (Windows only)
Short,
}
impl Default for FlagsFormat {
fn default() -> Self {
Self::Long
}
}
impl FlagsFormat {
pub(crate) fn deduce<V: Vars>(vars: &V) -> FlagsFormat {
vars.get(EZA_WINDOWS_ATTRIBUTES)
.and_then(|v| match v.to_ascii_lowercase().to_str() {
Some("short") => Some(FlagsFormat::Short),
Some("long") => Some(FlagsFormat::Long),
_ => None,
})
.unwrap_or_default()
}
}
/// Fields for which of a files time fields should be displayed in the
/// columns output.
///
@ -385,6 +415,7 @@ pub struct Table<'a> {
user_format: UserFormat,
#[cfg(unix)]
group_format: GroupFormat,
flags_format: FlagsFormat,
git: Option<&'a GitCache>,
}
@ -418,6 +449,7 @@ impl<'a> Table<'a> {
user_format: options.user_format,
#[cfg(unix)]
group_format: options.group_format,
flags_format: options.flags_format,
}
}
@ -519,7 +551,7 @@ impl<'a> Table<'a> {
),
#[cfg(unix)]
Column::SecurityContext => file.security_context().render(self.theme),
Column::FileFlags => file.flags().render(self.theme.ui.flags),
Column::FileFlags => file.flags().render(self.theme.ui.flags, self.flags_format),
Column::GitStatus => self.git_status(file).render(self.theme),
Column::SubdirGitRepo(status) => self.subdir_git_repo(file, status).render(self.theme),
#[cfg(unix)]