mirror of
https://github.com/eza-community/eza.git
synced 2025-08-04 17:08:42 +00:00
feat(flags): Add Windows file attributes
This commit is contained in:
parent
458a6f5b1e
commit
66d16a9c9a
10 changed files with 197 additions and 8 deletions
|
@ -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 file’s size of allocated file system blocks.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
138
src/output/render/flags_windows.rs
Normal file
138
src/output/render/flags_windows.rs
Normal 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)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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 file’s 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)]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue