Просмотр исходного кода

feat(flags): Add Windows file attributes

Robert Minsk 2 лет назад
Родитель
Сommit
66d16a9c9a

+ 1 - 1
man/eza.1.md

@@ -201,7 +201,7 @@ These options are available when running with `--long` (`-l`):
 : List numeric user and group IDs.
 : List numeric user and group IDs.
 
 
 `-O`, `--flags`
 `-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`
 `-S`, `--blocksize`
 : List each file’s size of allocated file system blocks.
 : List each file’s size of allocated file system blocks.

+ 7 - 1
src/fs/file.rs

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

+ 1 - 1
src/options/help.rs

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

+ 5 - 0
src/options/vars.rs

@@ -66,6 +66,11 @@ pub static EZA_ICONS_AUTO: &str = "EZA_ICONS_AUTO";
 
 
 pub static EZA_STDIN_SEPARATOR: &str = "EZA_STDIN_SEPARATOR";
 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`.
 /// Mockable wrapper for `std::env::var_os`.
 pub trait Vars {
 pub trait Vars {
     fn get(&self, name: &'static str) -> Option<OsString>;
     fn get(&self, name: &'static str) -> Option<OsString>;

+ 3 - 1
src/options/view.rs

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

+ 2 - 1
src/output/render/flags.rs

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

+ 2 - 1
src/output/render/flags_bsd.rs

@@ -3,6 +3,7 @@ use std::ffi::CStr;
 
 
 use crate::fs::fields as f;
 use crate::fs::fields as f;
 use crate::output::cell::TextCell;
 use crate::output::cell::TextCell;
+use crate::output::table::FlagsFormat;
 
 
 extern "C" {
 extern "C" {
     fn fflagstostr(flags: libc::c_ulong) -> *const libc::c_char;
     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 {
 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))
         TextCell::paint(style, flags_to_string(self.0))
     }
     }
 }
 }

+ 138 - 0
src/output/render/flags_windows.rs

@@ -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)
+            },
+        )
+    }
+}

+ 5 - 1
src/output/render/mod.rs

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

+ 33 - 1
src/output/table.rs

@@ -12,6 +12,8 @@ use uzers::UsersCache;
 
 
 use crate::fs::feature::git::GitCache;
 use crate::fs::feature::git::GitCache;
 use crate::fs::{fields as f, File};
 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::cell::TextCell;
 use crate::output::color_scale::ColorScaleInformation;
 use crate::output::color_scale::ColorScaleInformation;
 #[cfg(unix)]
 #[cfg(unix)]
@@ -29,6 +31,7 @@ pub struct Options {
     pub time_format: TimeFormat,
     pub time_format: TimeFormat,
     pub user_format: UserFormat,
     pub user_format: UserFormat,
     pub group_format: GroupFormat,
     pub group_format: GroupFormat,
+    pub flags_format: FlagsFormat,
     pub columns: Columns,
     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
 /// Fields for which of a file’s time fields should be displayed in the
 /// columns output.
 /// columns output.
 ///
 ///
@@ -385,6 +415,7 @@ pub struct Table<'a> {
     user_format: UserFormat,
     user_format: UserFormat,
     #[cfg(unix)]
     #[cfg(unix)]
     group_format: GroupFormat,
     group_format: GroupFormat,
+    flags_format: FlagsFormat,
     git: Option<&'a GitCache>,
     git: Option<&'a GitCache>,
 }
 }
 
 
@@ -418,6 +449,7 @@ impl<'a> Table<'a> {
             user_format: options.user_format,
             user_format: options.user_format,
             #[cfg(unix)]
             #[cfg(unix)]
             group_format: options.group_format,
             group_format: options.group_format,
+            flags_format: options.flags_format,
         }
         }
     }
     }
 
 
@@ -519,7 +551,7 @@ impl<'a> Table<'a> {
             ),
             ),
             #[cfg(unix)]
             #[cfg(unix)]
             Column::SecurityContext => file.security_context().render(self.theme),
             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::GitStatus => self.git_status(file).render(self.theme),
             Column::SubdirGitRepo(status) => self.subdir_git_repo(file, status).render(self.theme),
             Column::SubdirGitRepo(status) => self.subdir_git_repo(file, status).render(self.theme),
             #[cfg(unix)]
             #[cfg(unix)]