Преглед изворни кода

feat(flags): Add BSD file flags

Robert Minsk пре 2 година
родитељ
комит
458a6f5b1e

+ 3 - 0
man/eza.1.md

@@ -200,6 +200,9 @@ These options are available when running with `--long` (`-l`):
 `-n`, `--numeric`
 : 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)
+
 `-S`, `--blocksize`
 : List each file’s size of allocated file system blocks.
 

+ 3 - 0
man/eza_colors.5.md

@@ -300,6 +300,9 @@ LIST OF CODES
 `Sl`
 : SELinux level
 
+`ff`
+: BSD file flags
+
 Values in `EXA_COLORS` override those given in `LS_COLORS`, so you don’t need to re-write an existing `LS_COLORS` variable with proprietary extensions.
 
 

+ 7 - 0
src/fs/fields.rs

@@ -29,6 +29,9 @@ pub type time_t = i64;
 /// The type of a file’s user ID.
 pub type uid_t = u32;
 
+/// The type of user file flags
+pub type flag_t = u32;
+
 /// The file’s base type, which gets displayed in the very first column of the
 /// details output.
 ///
@@ -274,3 +277,7 @@ impl Default for SubdirGitRepo {
         }
     }
 }
+
+/// The user file flags on the file. This will only ever be a number;
+/// looking up the flags is done in the `display` module.
+pub struct Flags(pub flag_t);

+ 33 - 0
src/fs/file.rs

@@ -875,6 +875,39 @@ impl<'dir> File<'dir> {
             context: SecurityContextType::None,
         }
     }
+
+    /// User file flags.
+    #[cfg(any(
+        target_os = "macos",
+        target_os = "freebsd",
+        target_os = "netbsd",
+        target_os = "openbsd",
+        target_os = "dragonfly"
+    ))]
+    pub fn flags(&self) -> f::Flags {
+        #[cfg(target_os = "dragonfly")]
+        use std::os::dragonfly::fs::MetadataExt;
+        #[cfg(target_os = "freebsd")]
+        use std::os::freebsd::fs::MetadataExt;
+        #[cfg(target_os = "macos")]
+        use std::os::macos::fs::MetadataExt;
+        #[cfg(target_os = "netbsd")]
+        use std::os::netbsd::fs::MetadataExt;
+        #[cfg(target_os = "openbsd")]
+        use std::os::openbsd::fs::MetadataExt;
+        f::Flags(self.metadata.st_flags())
+    }
+
+    #[cfg(not(any(
+        target_os = "macos",
+        target_os = "freebsd",
+        target_os = "netbsd",
+        target_os = "openbsd",
+        target_os = "dragonfly"
+    )))]
+    pub fn flags(&self) -> f::Flags {
+        f::Flags(0)
+    }
 }
 
 impl<'a> AsRef<File<'a>> for File<'a> {

+ 2 - 1
src/options/flags.rs

@@ -81,6 +81,7 @@ pub static EXTENDED:          Arg = Arg { short: Some(b'@'), long: "extended",
 pub static OCTAL:             Arg = Arg { short: Some(b'o'), long: "octal-permissions",    takes_value: TakesValue::Forbidden };
 pub static SECURITY_CONTEXT:  Arg = Arg { short: Some(b'Z'), long: "context",              takes_value: TakesValue::Forbidden };
 pub static STDIN:             Arg = Arg { short: None,       long: "stdin",                takes_value: TakesValue::Forbidden };
+pub static FILE_FLAGS:        Arg = Arg { short: Some(b'O'), long: "flags",                takes_value: TakesValue::Forbidden };
 
 pub static ALL_ARGS: Args = Args(&[
     &VERSION, &HELP,
@@ -97,5 +98,5 @@ pub static ALL_ARGS: Args = Args(&[
     &NO_PERMISSIONS, &NO_FILESIZE, &NO_USER, &NO_TIME, &SMART_GROUP,
 
     &GIT, &NO_GIT, &GIT_REPOS, &GIT_REPOS_NO_STAT,
-    &EXTENDED, &OCTAL, &SECURITY_CONTEXT, &STDIN,
+    &EXTENDED, &OCTAL, &SECURITY_CONTEXT, &STDIN, &FILE_FLAGS
 ]);

+ 2 - 1
src/options/help.rs

@@ -59,8 +59,9 @@ LONG VIEW OPTIONS
   -H, --links                list each file's number of hard links
   -i, --inode                list each file's inode number
   -m, --modified             use the modified timestamp field
-  -M, --mounts               show mount details (Linux and MacOS only)
+  -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)
   -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

+ 2 - 0
src/options/view.rs

@@ -270,6 +270,7 @@ impl Columns {
         let links = matches.has(&flags::LINKS)?;
         let octal = matches.has(&flags::OCTAL)?;
         let security_context = xattr::ENABLED && matches.has(&flags::SECURITY_CONTEXT)?;
+        let file_flags = matches.has(&flags::FILE_FLAGS)?;
 
         let permissions = !matches.has(&flags::NO_PERMISSIONS)?;
         let filesize = !matches.has(&flags::NO_FILESIZE)?;
@@ -286,6 +287,7 @@ impl Columns {
             subdir_git_repos_no_stat,
             octal,
             security_context,
+            file_flags,
             permissions,
             filesize,
             user,

+ 10 - 0
src/output/render/flags.rs

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

+ 39 - 0
src/output/render/flags_bsd.rs

@@ -0,0 +1,39 @@
+use ansiterm::Style;
+use std::ffi::CStr;
+
+use crate::fs::fields as f;
+use crate::output::cell::TextCell;
+
+extern "C" {
+    fn fflagstostr(flags: libc::c_ulong) -> *const libc::c_char;
+}
+
+/// Wrapper around the C library call fflagstostr.  If returned string is NULL
+/// or empty a "-" is returned
+fn flags_to_string(flags: f::flag_t) -> String {
+    // SAFETY: Calling external "C" function
+    let flags_c_str = unsafe { fflagstostr(libc::c_ulong::from(flags)) };
+
+    if flags_c_str.is_null() {
+        "-".to_string()
+    } else {
+        let flags_str = unsafe { CStr::from_ptr(flags_c_str) };
+        let flags = flags_str
+            .to_str()
+            .map_or("-", |s| if s.is_empty() { "-" } else { s })
+            .to_string();
+
+        // SAFETY: Calling external "C" function to free memory allocated by fflagstostr
+        unsafe {
+            libc::free(flags_c_str.cast_mut().cast());
+        }
+
+        flags
+    }
+}
+
+impl f::Flags {
+    pub fn render(self, style: Style) -> TextCell {
+        TextCell::paint(style, flags_to_string(self.0))
+    }
+}

+ 18 - 0
src/output/render/mod.rs

@@ -45,3 +45,21 @@ pub use self::octal::Render as OctalPermissionsRender;
 
 mod securityctx;
 pub use self::securityctx::Colours as SecurityCtxColours;
+
+#[cfg(any(
+    target_os = "macos",
+    target_os = "freebsd",
+    target_os = "netbsd",
+    target_os = "openbsd",
+    target_os = "dragonfly"
+))]
+mod flags_bsd;
+
+#[cfg(not(any(
+    target_os = "macos",
+    target_os = "freebsd",
+    target_os = "netbsd",
+    target_os = "openbsd",
+    target_os = "dragonfly"
+)))]
+mod flags;

+ 8 - 0
src/output/table.rs

@@ -49,6 +49,7 @@ pub struct Columns {
     pub subdir_git_repos_no_stat: bool,
     pub octal: bool,
     pub security_context: bool,
+    pub file_flags: bool,
 
     // Defaults to true:
     pub permissions: bool,
@@ -98,6 +99,10 @@ impl Columns {
             columns.push(Column::Group);
         }
 
+        if self.file_flags {
+            columns.push(Column::FileFlags);
+        }
+
         #[cfg(target_os = "linux")]
         if self.security_context {
             columns.push(Column::SecurityContext);
@@ -157,6 +162,7 @@ pub enum Column {
     Octal,
     #[cfg(unix)]
     SecurityContext,
+    FileFlags,
 }
 
 /// Each column can pick its own **Alignment**. Usually, numbers are
@@ -214,6 +220,7 @@ impl Column {
             Self::Octal => "Octal",
             #[cfg(unix)]
             Self::SecurityContext => "Security Context",
+            Self::FileFlags => "Flags",
         }
     }
 }
@@ -512,6 +519,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::GitStatus => self.git_status(file).render(self.theme),
             Column::SubdirGitRepo(status) => self.subdir_git_repo(file, status).render(self.theme),
             #[cfg(unix)]

+ 1 - 0
src/theme/default_theme.rs

@@ -113,6 +113,7 @@ impl UiStyles {
             inode: Purple.normal(),
             blocks: Cyan.normal(),
             octal: Purple.normal(),
+            flags: Style::default(),
             header: Style::default().underline(),
 
             symlink_path: Cyan.normal(),

+ 1 - 0
src/theme/mod.rs

@@ -583,6 +583,7 @@ mod customs_test {
     test!(exa_lp:  ls "", exa "lp=38;5;133"  =>  colours c -> { c.symlink_path                          = Fixed(133).normal(); });
     test!(exa_cc:  ls "", exa "cc=38;5;134"  =>  colours c -> { c.control_char                          = Fixed(134).normal(); });
     test!(exa_oc:  ls "", exa "oc=38;5;135"  =>  colours c -> { c.octal                                 = Fixed(135).normal(); });
+    test!(exa_ff:  ls "", exa "ff=38;5;136"  =>  colours c -> { c.flags                                 = Fixed(136).normal(); });
     test!(exa_bo:  ls "", exa "bO=4"         =>  colours c -> { c.broken_path_overlay                   = Style::default().underline(); });
 
     test!(exa_mp:  ls "", exa "mp=1;34;4"    =>  colours c -> { c.filekinds.mount_point                 = Blue.bold().underline(); });

+ 2 - 0
src/theme/ui_styles.rs

@@ -23,6 +23,7 @@ pub struct UiStyles {
     pub blocks:       Style,          // bl
     pub header:       Style,          // hd
     pub octal:        Style,          // oc
+    pub flags:        Style,          // ff
 
     pub symlink_path:         Style,  // lp
     pub control_char:         Style,  // cc
@@ -253,6 +254,7 @@ impl UiStyles {
             "bl" => self.blocks                         = pair.to_style(),
             "hd" => self.header                         = pair.to_style(),
             "oc" => self.octal                          = pair.to_style(),
+            "ff" => self.flags                          = pair.to_style(),
             "lp" => self.symlink_path                   = pair.to_style(),
             "cc" => self.control_char                   = pair.to_style(),
             "bO" => self.broken_path_overlay            = pair.to_style(),