Переглянути джерело

Merge pull request #514 from liketurbo/feat-480

feat: add option --smart-group
Christina Sørensen 2 роки тому
батько
коміт
e58106a35b

+ 1 - 1
completions/bash/eza

@@ -4,7 +4,7 @@ _eza() {
     prev=${COMP_WORDS[COMP_CWORD-1]}
     prev=${COMP_WORDS[COMP_CWORD-1]}
 
 
     case "$prev" in
     case "$prev" in
-        --help|-v|--version)
+        --help|-v|--version|--smart-group)
             return
             return
             ;;
             ;;
 
 

+ 1 - 0
completions/fish/eza.fish

@@ -24,6 +24,7 @@ complete -c eza -l icons -d "Display icons"
 complete -c eza -l no-icons -d "Don't display icons"
 complete -c eza -l no-icons -d "Don't display icons"
 complete -c eza -l no-quotes -d "Don't quote file names with spaces"
 complete -c eza -l no-quotes -d "Don't quote file names with spaces"
 complete -c eza -l hyperlink -d "Display entries as hyperlinks"
 complete -c eza -l hyperlink -d "Display entries as hyperlinks"
+complete -c eza -l smart-group -d "Only show group if it has a different name from owner"
 
 
 # Filtering and sorting options
 # Filtering and sorting options
 complete -c eza -l group-directories-first -d "Sort directories before other files"
 complete -c eza -l group-directories-first -d "Sort directories before other files"

+ 1 - 0
completions/nush/eza.nu

@@ -55,4 +55,5 @@ export extern "eza" [
     --git-repos-no-status      # List each git-repos branch name (much faster)
     --git-repos-no-status      # List each git-repos branch name (much faster)
     --extended(-@)             # List each file's extended attributes and sizes
     --extended(-@)             # List each file's extended attributes and sizes
     --context(-Z)              # List each file's security context
     --context(-Z)              # List each file's security context
+    --smart-group              # Only show group if it has a different name from owner
 ]
 ]

+ 2 - 1
completions/zsh/_eza

@@ -65,7 +65,8 @@ __eza() {
         {-@,--extended}"[List each file's extended attributes and sizes]" \
         {-@,--extended}"[List each file's extended attributes and sizes]" \
         {-Z,--context}"[List each file's security context]" \
         {-Z,--context}"[List each file's security context]" \
         {-M,--mounts}"[Show mount details (long mode only)]" \
         {-M,--mounts}"[Show mount details (long mode only)]" \
-        '*:filename:_files'
+        '*:filename:_files' \
+        --smart-group"[Only show group if it has a different name from owner]"
 }
 }
 
 
 __eza
 __eza

+ 3 - 0
man/eza.1.md

@@ -103,6 +103,9 @@ Manually setting this option overrides `NO_COLOR` environment.
 `-w`, `--width=COLS`
 `-w`, `--width=COLS`
 : Set screen width in columns.
 : Set screen width in columns.
 
 
+`--smart-group`
+: Only show group if it has a different name from owner
+
 
 
 FILTERING AND SORTING OPTIONS
 FILTERING AND SORTING OPTIONS
 =============================
 =============================

+ 19 - 18
src/options/flags.rs

@@ -40,23 +40,24 @@ const SORTS: Values = &[ "name", "Name", "size", "extension",
                          "created", "inode", "type", "none" ];
                          "created", "inode", "type", "none" ];
 
 
 // display options
 // display options
-pub static BINARY:     Arg = Arg { short: Some(b'b'), long: "binary",     takes_value: TakesValue::Forbidden };
-pub static BYTES:      Arg = Arg { short: Some(b'B'), long: "bytes",      takes_value: TakesValue::Forbidden };
-pub static GROUP:      Arg = Arg { short: Some(b'g'), long: "group",      takes_value: TakesValue::Forbidden };
-pub static NUMERIC:    Arg = Arg { short: Some(b'n'), long: "numeric",    takes_value: TakesValue::Forbidden };
-pub static HEADER:     Arg = Arg { short: Some(b'h'), long: "header",     takes_value: TakesValue::Forbidden };
-pub static ICONS:      Arg = Arg { short: None,       long: "icons",      takes_value: TakesValue::Forbidden };
-pub static INODE:      Arg = Arg { short: Some(b'i'), long: "inode",      takes_value: TakesValue::Forbidden };
-pub static LINKS:      Arg = Arg { short: Some(b'H'), long: "links",      takes_value: TakesValue::Forbidden };
-pub static MODIFIED:   Arg = Arg { short: Some(b'm'), long: "modified",   takes_value: TakesValue::Forbidden };
-pub static CHANGED:    Arg = Arg { short: None,       long: "changed",    takes_value: TakesValue::Forbidden };
-pub static BLOCKSIZE:  Arg = Arg { short: Some(b'S'), long: "blocksize",  takes_value: TakesValue::Forbidden };
-pub static TIME:       Arg = Arg { short: Some(b't'), long: "time",       takes_value: TakesValue::Necessary(Some(TIMES)) };
-pub static ACCESSED:   Arg = Arg { short: Some(b'u'), long: "accessed",   takes_value: TakesValue::Forbidden };
-pub static CREATED:    Arg = Arg { short: Some(b'U'), long: "created",    takes_value: TakesValue::Forbidden };
-pub static TIME_STYLE: Arg = Arg { short: None,       long: "time-style", takes_value: TakesValue::Necessary(Some(TIME_STYLES)) };
-pub static HYPERLINK:  Arg = Arg { short: None,       long: "hyperlink",  takes_value: TakesValue::Forbidden };
-pub static MOUNTS:     Arg = Arg { short: Some(b'M'), long: "mounts",     takes_value: TakesValue::Forbidden };
+pub static BINARY:      Arg = Arg { short: Some(b'b'), long: "binary",      takes_value: TakesValue::Forbidden };
+pub static BYTES:       Arg = Arg { short: Some(b'B'), long: "bytes",       takes_value: TakesValue::Forbidden };
+pub static GROUP:       Arg = Arg { short: Some(b'g'), long: "group",       takes_value: TakesValue::Forbidden };
+pub static NUMERIC:     Arg = Arg { short: Some(b'n'), long: "numeric",     takes_value: TakesValue::Forbidden };
+pub static HEADER:      Arg = Arg { short: Some(b'h'), long: "header",      takes_value: TakesValue::Forbidden };
+pub static ICONS:       Arg = Arg { short: None,       long: "icons",       takes_value: TakesValue::Forbidden };
+pub static INODE:       Arg = Arg { short: Some(b'i'), long: "inode",       takes_value: TakesValue::Forbidden };
+pub static LINKS:       Arg = Arg { short: Some(b'H'), long: "links",       takes_value: TakesValue::Forbidden };
+pub static MODIFIED:    Arg = Arg { short: Some(b'm'), long: "modified",    takes_value: TakesValue::Forbidden };
+pub static CHANGED:     Arg = Arg { short: None,       long: "changed",     takes_value: TakesValue::Forbidden };
+pub static BLOCKSIZE:   Arg = Arg { short: Some(b'S'), long: "blocksize",   takes_value: TakesValue::Forbidden };
+pub static TIME:        Arg = Arg { short: Some(b't'), long: "time",        takes_value: TakesValue::Necessary(Some(TIMES)) };
+pub static ACCESSED:    Arg = Arg { short: Some(b'u'), long: "accessed",    takes_value: TakesValue::Forbidden };
+pub static CREATED:     Arg = Arg { short: Some(b'U'), long: "created",     takes_value: TakesValue::Forbidden };
+pub static TIME_STYLE:  Arg = Arg { short: None,       long: "time-style",  takes_value: TakesValue::Necessary(Some(TIME_STYLES)) };
+pub static HYPERLINK:   Arg = Arg { short: None,       long: "hyperlink",   takes_value: TakesValue::Forbidden };
+pub static MOUNTS:      Arg = Arg { short: Some(b'M'), long: "mounts",      takes_value: TakesValue::Forbidden };
+pub static SMART_GROUP: Arg = Arg { short: None,       long: "smart-group", takes_value: TakesValue::Forbidden };
 const TIMES: Values = &["modified", "changed", "accessed", "created"];
 const TIMES: Values = &["modified", "changed", "accessed", "created"];
 const TIME_STYLES: Values = &["default", "long-iso", "full-iso", "iso", "relative"];
 const TIME_STYLES: Values = &["default", "long-iso", "full-iso", "iso", "relative"];
 
 
@@ -87,7 +88,7 @@ pub static ALL_ARGS: Args = Args(&[
 
 
     &BINARY, &BYTES, &GROUP, &NUMERIC, &HEADER, &ICONS, &INODE, &LINKS, &MODIFIED, &CHANGED,
     &BINARY, &BYTES, &GROUP, &NUMERIC, &HEADER, &ICONS, &INODE, &LINKS, &MODIFIED, &CHANGED,
     &BLOCKSIZE, &TIME, &ACCESSED, &CREATED, &TIME_STYLE, &HYPERLINK, &MOUNTS,
     &BLOCKSIZE, &TIME, &ACCESSED, &CREATED, &TIME_STYLE, &HYPERLINK, &MOUNTS,
-    &NO_PERMISSIONS, &NO_FILESIZE, &NO_USER, &NO_TIME, &NO_ICONS,
+    &NO_PERMISSIONS, &NO_FILESIZE, &NO_USER, &NO_TIME, &NO_ICONS, &SMART_GROUP,
 
 
     &GIT, &NO_GIT, &GIT_REPOS, &GIT_REPOS_NO_STAT,
     &GIT, &NO_GIT, &GIT_REPOS, &GIT_REPOS_NO_STAT,
     &EXTENDED, &OCTAL, &SECURITY_CONTEXT
     &EXTENDED, &OCTAL, &SECURITY_CONTEXT

+ 1 - 0
src/options/help.rs

@@ -27,6 +27,7 @@ DISPLAY OPTIONS
   --no-quotes        don't quote file names with spaces
   --no-quotes        don't quote file names with spaces
   --hyperlink        display entries as hyperlinks
   --hyperlink        display entries as hyperlinks
   -w, --width COLS   set screen width in columns
   -w, --width COLS   set screen width in columns
+  --smart-group      only show group if it has a different name from owner
 
 
 
 
 FILTERING AND SORTING OPTIONS
 FILTERING AND SORTING OPTIONS

+ 12 - 1
src/options/view.rs

@@ -3,7 +3,9 @@ use crate::options::parser::MatchedFlags;
 use crate::options::{flags, NumberSource, OptionsError, Vars};
 use crate::options::{flags, NumberSource, OptionsError, Vars};
 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::{Columns, Options as TableOptions, SizeFormat, TimeTypes, UserFormat};
+use crate::output::table::{
+    Columns, 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};
 
 
@@ -231,11 +233,13 @@ impl TableOptions {
         let time_format = TimeFormat::deduce(matches, vars)?;
         let time_format = TimeFormat::deduce(matches, vars)?;
         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 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,
             columns,
             columns,
         })
         })
     }
     }
@@ -341,6 +345,13 @@ impl UserFormat {
     }
     }
 }
 }
 
 
+impl GroupFormat {
+    fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {
+        let flag = matches.has(&flags::SMART_GROUP)?;
+        Ok(if flag { Self::Smart } else { Self::Regular })
+    }
+}
+
 impl TimeTypes {
 impl TimeTypes {
     /// Determine which of a file’s time fields should be displayed for it
     /// Determine which of a file’s time fields should be displayed for it
     /// based on the user’s options.
     /// based on the user’s options.

+ 73 - 12
src/output/render/groups.rs

@@ -3,14 +3,15 @@ use uzers::{Groups, Users};
 
 
 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::UserFormat;
+use crate::output::table::{GroupFormat, UserFormat};
 
 
 pub trait Render {
 pub trait Render {
     fn render<C: Colours, U: Users + Groups>(
     fn render<C: Colours, U: Users + Groups>(
         self,
         self,
         colours: &C,
         colours: &C,
         users: &U,
         users: &U,
-        format: UserFormat,
+        user_format: UserFormat,
+        group_format: GroupFormat,
     ) -> TextCell;
     ) -> TextCell;
 }
 }
 
 
@@ -19,7 +20,8 @@ impl Render for Option<f::Group> {
         self,
         self,
         colours: &C,
         colours: &C,
         users: &U,
         users: &U,
-        format: UserFormat,
+        user_format: UserFormat,
+        group_format: GroupFormat,
     ) -> TextCell {
     ) -> TextCell {
         use uzers::os::unix::GroupExt;
         use uzers::os::unix::GroupExt;
 
 
@@ -46,11 +48,26 @@ impl Render for Option<f::Group> {
             style = colours.root_group();
             style = colours.root_group();
         }
         }
 
 
-        let group_name = match format {
+        let mut group_name = match user_format {
             UserFormat::Name => group.name().to_string_lossy().into(),
             UserFormat::Name => group.name().to_string_lossy().into(),
             UserFormat::Numeric => group.gid().to_string(),
             UserFormat::Numeric => group.gid().to_string(),
         };
         };
 
 
+        group_name = match group_format {
+            GroupFormat::Smart => {
+                if let Some(current_user) = users.get_user_by_uid(current_uid) {
+                    if current_user.name() == group.name() {
+                        ":".to_string()
+                    } else {
+                        group_name
+                    }
+                } else {
+                    group_name
+                }
+            }
+            GroupFormat::Regular => group_name,
+        };
+
         TextCell::paint(style, group_name)
         TextCell::paint(style, group_name)
     }
     }
 }
 }
@@ -68,7 +85,7 @@ pub mod test {
     use super::{Colours, Render};
     use super::{Colours, Render};
     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::UserFormat;
+    use crate::output::table::{GroupFormat, UserFormat};
 
 
     use ansiterm::Colour::*;
     use ansiterm::Colour::*;
     use ansiterm::Style;
     use ansiterm::Style;
@@ -95,13 +112,18 @@ pub mod test {
         let expected = TextCell::paint_str(Fixed(81).normal(), "folk");
         let expected = TextCell::paint_str(Fixed(81).normal(), "folk");
         assert_eq!(
         assert_eq!(
             expected,
             expected,
-            group.render(&TestColours, &users, UserFormat::Name)
+            group.render(&TestColours, &users, UserFormat::Name, GroupFormat::Regular)
         );
         );
 
 
         let expected = TextCell::paint_str(Fixed(81).normal(), "100");
         let expected = TextCell::paint_str(Fixed(81).normal(), "100");
         assert_eq!(
         assert_eq!(
             expected,
             expected,
-            group.render(&TestColours, &users, UserFormat::Numeric)
+            group.render(
+                &TestColours,
+                &users,
+                UserFormat::Numeric,
+                GroupFormat::Regular
+            )
         );
         );
     }
     }
 
 
@@ -113,11 +135,16 @@ pub mod test {
         let expected = TextCell::paint_str(Fixed(81).normal(), "100");
         let expected = TextCell::paint_str(Fixed(81).normal(), "100");
         assert_eq!(
         assert_eq!(
             expected,
             expected,
-            group.render(&TestColours, &users, UserFormat::Name)
+            group.render(&TestColours, &users, UserFormat::Name, GroupFormat::Regular)
         );
         );
         assert_eq!(
         assert_eq!(
             expected,
             expected,
-            group.render(&TestColours, &users, UserFormat::Numeric)
+            group.render(
+                &TestColours,
+                &users,
+                UserFormat::Numeric,
+                GroupFormat::Regular
+            )
         );
         );
     }
     }
 
 
@@ -131,7 +158,7 @@ pub mod test {
         let expected = TextCell::paint_str(Fixed(80).normal(), "folk");
         let expected = TextCell::paint_str(Fixed(80).normal(), "folk");
         assert_eq!(
         assert_eq!(
             expected,
             expected,
-            group.render(&TestColours, &users, UserFormat::Name)
+            group.render(&TestColours, &users, UserFormat::Name, GroupFormat::Regular)
         )
         )
     }
     }
 
 
@@ -147,7 +174,7 @@ pub mod test {
         let expected = TextCell::paint_str(Fixed(80).normal(), "folk");
         let expected = TextCell::paint_str(Fixed(80).normal(), "folk");
         assert_eq!(
         assert_eq!(
             expected,
             expected,
-            group.render(&TestColours, &users, UserFormat::Name)
+            group.render(&TestColours, &users, UserFormat::Name, GroupFormat::Regular)
         )
         )
     }
     }
 
 
@@ -160,8 +187,42 @@ pub mod test {
             group.render(
             group.render(
                 &TestColours,
                 &TestColours,
                 &MockUsers::with_current_uid(0),
                 &MockUsers::with_current_uid(0),
-                UserFormat::Numeric
+                UserFormat::Numeric,
+                GroupFormat::Regular
             )
             )
         );
         );
     }
     }
+
+    #[test]
+    fn smart() {
+        let mut users = MockUsers::with_current_uid(1000);
+        users.add_user(User::new(1000, "user", 110));
+        users.add_group(Group::new(100, "user"));
+        users.add_group(Group::new(101, "http"));
+
+        let same_group = Some(f::Group(100));
+        let expected = TextCell::paint_str(Fixed(81).normal(), ":");
+        assert_eq!(
+            expected,
+            same_group.render(&TestColours, &users, UserFormat::Name, GroupFormat::Smart)
+        );
+
+        let expected = TextCell::paint_str(Fixed(81).normal(), ":");
+        assert_eq!(
+            expected,
+            same_group.render(
+                &TestColours,
+                &users,
+                UserFormat::Numeric,
+                GroupFormat::Smart
+            )
+        );
+
+        let http_group = Some(f::Group(101));
+        let expected = TextCell::paint_str(Fixed(81).normal(), "http");
+        assert_eq!(
+            expected,
+            http_group.render(&TestColours, &users, UserFormat::Name, GroupFormat::Smart)
+        );
+    }
 }
 }

+ 20 - 4
src/output/table.rs

@@ -25,6 +25,7 @@ pub struct Options {
     pub size_format: SizeFormat,
     pub size_format: SizeFormat,
     pub time_format: TimeFormat,
     pub time_format: TimeFormat,
     pub user_format: UserFormat,
     pub user_format: UserFormat,
+    pub group_format: GroupFormat,
     pub columns: Columns,
     pub columns: Columns,
 }
 }
 
 
@@ -239,6 +240,15 @@ pub enum UserFormat {
     Name,
     Name,
 }
 }
 
 
+/// Formatting options for group only.
+#[derive(PartialEq, Eq, Debug, Copy, Clone)]
+pub enum GroupFormat {
+    /// Numeric or text value
+    Regular,
+    /// Show ":" if user-group value is the same
+    Smart,
+}
+
 impl Default for SizeFormat {
 impl Default for SizeFormat {
     fn default() -> Self {
     fn default() -> Self {
         Self::DecimalBytes
         Self::DecimalBytes
@@ -355,6 +365,8 @@ pub struct Table<'a> {
     size_format: SizeFormat,
     size_format: SizeFormat,
     #[cfg(unix)]
     #[cfg(unix)]
     user_format: UserFormat,
     user_format: UserFormat,
+    #[cfg(unix)]
+    group_format: GroupFormat,
     git: Option<&'a GitCache>,
     git: Option<&'a GitCache>,
 }
 }
 
 
@@ -379,6 +391,8 @@ impl<'a> Table<'a> {
             size_format: options.size_format,
             size_format: options.size_format,
             #[cfg(unix)]
             #[cfg(unix)]
             user_format: options.user_format,
             user_format: options.user_format,
+            #[cfg(unix)]
+            group_format: options.group_format,
         }
         }
     }
     }
 
 
@@ -457,10 +471,12 @@ impl<'a> Table<'a> {
                     .render(self.theme, &*self.env.lock_users(), self.user_format)
                     .render(self.theme, &*self.env.lock_users(), self.user_format)
             }
             }
             #[cfg(unix)]
             #[cfg(unix)]
-            Column::Group => {
-                file.group()
-                    .render(self.theme, &*self.env.lock_users(), self.user_format)
-            }
+            Column::Group => file.group().render(
+                self.theme,
+                &*self.env.lock_users(),
+                self.user_format,
+                self.group_format,
+            ),
             #[cfg(unix)]
             #[cfg(unix)]
             Column::SecurityContext => file.security_context().render(self.theme),
             Column::SecurityContext => file.security_context().render(self.theme),
             Column::GitStatus => self.git_status(file).render(self.theme),
             Column::GitStatus => self.git_status(file).render(self.theme),