瀏覽代碼

Merge pull request #813 from b05902132/numeric_gid_uid

Add options for -n or --numeric-gid-uid.
Benjamin Sago 4 年之前
父節點
當前提交
c1435411c3

+ 1 - 0
completions/completions.fish

@@ -66,6 +66,7 @@ complete -c exa -s 't' -l 'time'  -x -d "Which timestamp field to list" -a "
     created\t'Display created time'
 "
 complete -c exa -s 'm' -l 'modified'      -d "Use the modified timestamp field"
+complete -c exa -s 'n' -l 'numeric'       -d "List numeric user and group IDs."
 complete -c exa        -l 'changed'       -d "Use the changed timestamp field"
 complete -c exa -s 'u' -l 'accessed'      -d "Use the accessed timestamp field"
 complete -c exa -s 'U' -l 'created'       -d "Use the created timestamp field"

+ 1 - 0
completions/completions.zsh

@@ -40,6 +40,7 @@ __exa() {
         {-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]" \
+        {-n, --numeric}"[List numeric user and group IDs.]" \
         {-S,--blocks}"[List each file's number of filesystem blocks]" \
         {-t,--time}="[Which time field to show]:(time field):(accessed changed created modified)" \
         --time-style="[How to format timestamps]:(time style):(default iso long-iso full-iso)" \

+ 3 - 0
man/exa.1.md

@@ -143,6 +143,9 @@ These options are available when running with `--long` (`-l`):
 `-m`, `--modified`
 : Use the modified timestamp field.
 
+`-n`, `--numeric`
+: List numeric user and group IDs.
+
 `-S`, `--blocks`
 : List each file’s number of file system blocks.
 

+ 2 - 1
src/options/flags.rs

@@ -39,6 +39,7 @@ const SORTS: Values = &[ "name", "Name", "size", "extension",
 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 };
@@ -75,7 +76,7 @@ pub static ALL_ARGS: Args = Args(&[
     &ALL, &LIST_DIRS, &LEVEL, &REVERSE, &SORT, &DIRS_FIRST,
     &IGNORE_GLOB, &GIT_IGNORE, &ONLY_DIRS,
 
-    &BINARY, &BYTES, &GROUP, &HEADER, &ICONS, &INODE, &LINKS, &MODIFIED, &CHANGED,
+    &BINARY, &BYTES, &GROUP, &NUMERIC, &HEADER, &ICONS, &INODE, &LINKS, &MODIFIED, &CHANGED,
     &BLOCKS, &TIME, &ACCESSED, &CREATED, &TIME_STYLE,
     &NO_PERMISSIONS, &NO_FILESIZE, &NO_USER, &NO_TIME, &NO_ICONS,
 

+ 1 - 0
src/options/help.rs

@@ -48,6 +48,7 @@ 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
+  -n, --numeric        list numeric user and group IDs
   -S, --blocks         show number of file system blocks
   -t, --time FIELD     which timestamp field to list (modified, accessed, created)
   -u, --accessed       use the accessed timestamp field

+ 30 - 18
src/options/view.rs

@@ -4,7 +4,7 @@ use crate::options::parser::MatchedFlags;
 use crate::output::{View, Mode, TerminalWidth, grid, details};
 use crate::output::grid_details::{self, RowThreshold};
 use crate::output::file_name::Options as FileStyle;
-use crate::output::table::{TimeTypes, SizeFormat, Columns, Options as TableOptions};
+use crate::output::table::{TimeTypes, SizeFormat, UserFormat, Columns, Options as TableOptions};
 use crate::output::time::TimeFormat;
 
 
@@ -85,7 +85,7 @@ impl Mode {
         // user about flags that won’t have any effect.
         if matches.is_strict() {
             for option in &[ &flags::BINARY, &flags::BYTES, &flags::INODE, &flags::LINKS,
-                             &flags::HEADER, &flags::BLOCKS, &flags::TIME, &flags::GROUP ] {
+                             &flags::HEADER, &flags::BLOCKS, &flags::TIME, &flags::GROUP, &flags::NUMERIC ] {
                 if matches.has(option)? {
                     return Err(OptionsError::Useless(*option, false, &flags::LONG));
                 }
@@ -183,8 +183,9 @@ impl TableOptions {
     fn deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self, OptionsError> {
         let time_format = TimeFormat::deduce(matches, vars)?;
         let size_format = SizeFormat::deduce(matches)?;
+        let user_format = UserFormat::deduce(matches)?;
         let columns = Columns::deduce(matches)?;
-        Ok(Self { time_format, size_format, columns })
+        Ok(Self { time_format, size_format, columns , user_format})
     }
 }
 
@@ -266,6 +267,14 @@ impl TimeFormat {
 }
 
 
+impl UserFormat {
+    fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {
+        let flag = matches.has(&flags::NUMERIC)?;
+        Ok(if flag { Self::Numeric } else { Self::Name })
+    }
+}
+
+
 impl TimeTypes {
 
     /// Determine which of a file’s time fields should be displayed for it
@@ -345,7 +354,8 @@ mod test {
                                    &flags::CREATED, &flags::ACCESSED,
                                    &flags::HEADER, &flags::GROUP,  &flags::INODE, &flags::GIT,
                                    &flags::LINKS,  &flags::BLOCKS, &flags::LONG,  &flags::LEVEL,
-                                   &flags::GRID,   &flags::ACROSS, &flags::ONE_LINE, &flags::TREE ];
+                                   &flags::GRID,   &flags::ACROSS, &flags::ONE_LINE, &flags::TREE,
+                                   &flags::NUMERIC ];
 
     macro_rules! test {
 
@@ -547,24 +557,26 @@ mod test {
         test!(long_across:   Mode <- ["--long", "--across"],   None;  Last => like Ok(Mode::Details(_)));
 
         // Options that do nothing without --long
-        test!(just_header:   Mode <- ["--header"], None;  Last => like Ok(Mode::Grid(_)));
-        test!(just_group:    Mode <- ["--group"],  None;  Last => like Ok(Mode::Grid(_)));
-        test!(just_inode:    Mode <- ["--inode"],  None;  Last => like Ok(Mode::Grid(_)));
-        test!(just_links:    Mode <- ["--links"],  None;  Last => like Ok(Mode::Grid(_)));
-        test!(just_blocks:   Mode <- ["--blocks"], None;  Last => like Ok(Mode::Grid(_)));
-        test!(just_binary:   Mode <- ["--binary"], None;  Last => like Ok(Mode::Grid(_)));
-        test!(just_bytes:    Mode <- ["--bytes"],  None;  Last => like Ok(Mode::Grid(_)));
+        test!(just_header:   Mode <- ["--header"],   None;  Last => like Ok(Mode::Grid(_)));
+        test!(just_group:    Mode <- ["--group"],    None;  Last => like Ok(Mode::Grid(_)));
+        test!(just_inode:    Mode <- ["--inode"],    None;  Last => like Ok(Mode::Grid(_)));
+        test!(just_links:    Mode <- ["--links"],    None;  Last => like Ok(Mode::Grid(_)));
+        test!(just_blocks:   Mode <- ["--blocks"],   None;  Last => like Ok(Mode::Grid(_)));
+        test!(just_binary:   Mode <- ["--binary"],   None;  Last => like Ok(Mode::Grid(_)));
+        test!(just_bytes:    Mode <- ["--bytes"],    None;  Last => like Ok(Mode::Grid(_)));
+        test!(just_numeric:  Mode <- ["--numeric"],  None;  Last => like Ok(Mode::Grid(_)));
 
         #[cfg(feature = "git")]
         test!(just_git:      Mode <- ["--git"],    None;  Last => like Ok(Mode::Grid(_)));
 
-        test!(just_header_2: Mode <- ["--header"], None;  Complain => err OptionsError::Useless(&flags::HEADER, false, &flags::LONG));
-        test!(just_group_2:  Mode <- ["--group"],  None;  Complain => err OptionsError::Useless(&flags::GROUP,  false, &flags::LONG));
-        test!(just_inode_2:  Mode <- ["--inode"],  None;  Complain => err OptionsError::Useless(&flags::INODE,  false, &flags::LONG));
-        test!(just_links_2:  Mode <- ["--links"],  None;  Complain => err OptionsError::Useless(&flags::LINKS,  false, &flags::LONG));
-        test!(just_blocks_2: Mode <- ["--blocks"], None;  Complain => err OptionsError::Useless(&flags::BLOCKS, false, &flags::LONG));
-        test!(just_binary_2: Mode <- ["--binary"], None;  Complain => err OptionsError::Useless(&flags::BINARY, false, &flags::LONG));
-        test!(just_bytes_2:  Mode <- ["--bytes"],  None;  Complain => err OptionsError::Useless(&flags::BYTES,  false, &flags::LONG));
+        test!(just_header_2: Mode <- ["--header"],   None;  Complain => err OptionsError::Useless(&flags::HEADER,  false, &flags::LONG));
+        test!(just_group_2:  Mode <- ["--group"],    None;  Complain => err OptionsError::Useless(&flags::GROUP,   false, &flags::LONG));
+        test!(just_inode_2:  Mode <- ["--inode"],    None;  Complain => err OptionsError::Useless(&flags::INODE,   false, &flags::LONG));
+        test!(just_links_2:  Mode <- ["--links"],    None;  Complain => err OptionsError::Useless(&flags::LINKS,   false, &flags::LONG));
+        test!(just_blocks_2: Mode <- ["--blocks"],   None;  Complain => err OptionsError::Useless(&flags::BLOCKS,  false, &flags::LONG));
+        test!(just_binary_2: Mode <- ["--binary"],   None;  Complain => err OptionsError::Useless(&flags::BINARY,  false, &flags::LONG));
+        test!(just_bytes_2:  Mode <- ["--bytes"],    None;  Complain => err OptionsError::Useless(&flags::BYTES,   false, &flags::LONG));
+        test!(just_numeric2: Mode <- ["--numeric"],  None;  Complain => err OptionsError::Useless(&flags::NUMERIC, false, &flags::LONG));
 
         #[cfg(feature = "git")]
         test!(just_git_2:    Mode <- ["--git"],    None;  Complain => err OptionsError::Useless(&flags::GIT,    false, &flags::LONG));

+ 19 - 7
src/output/render/groups.rs

@@ -3,10 +3,11 @@ use users::{Users, Groups};
 
 use crate::fs::fields as f;
 use crate::output::cell::TextCell;
+use crate::output::table::UserFormat;
 
 
 impl f::Group {
-    pub fn render<C: Colours, U: Users+Groups>(self, colours: &C, users: &U) -> TextCell {
+    pub fn render<C: Colours, U: Users+Groups>(self, colours: &C, users: &U, format: UserFormat) -> TextCell {
         use users::os::unix::GroupExt;
 
         let mut style = colours.not_yours();
@@ -26,7 +27,12 @@ impl f::Group {
             }
         }
 
-        TextCell::paint(style, group.name().to_string_lossy().into())
+        let group_name = match format {
+            UserFormat::Name => group.name().to_string_lossy().into(),
+            UserFormat::Numeric => group.gid().to_string(),
+        };
+
+        TextCell::paint(style, group_name)
     }
 }
 
@@ -43,6 +49,7 @@ pub mod test {
     use super::Colours;
     use crate::fs::fields as f;
     use crate::output::cell::TextCell;
+    use crate::output::table::UserFormat;
 
     use users::{User, Group};
     use users::mock::MockUsers;
@@ -66,16 +73,21 @@ pub mod test {
 
         let group = f::Group(100);
         let expected = TextCell::paint_str(Fixed(81).normal(), "folk");
-        assert_eq!(expected, group.render(&TestColours, &users))
+        assert_eq!(expected, group.render(&TestColours, &users, UserFormat::Name));
+
+        let expected = TextCell::paint_str(Fixed(81).normal(), "100");
+        assert_eq!(expected, group.render(&TestColours, &users, UserFormat::Numeric));
     }
 
+
     #[test]
     fn unnamed() {
         let users = MockUsers::with_current_uid(1000);
 
         let group = f::Group(100);
         let expected = TextCell::paint_str(Fixed(81).normal(), "100");
-        assert_eq!(expected, group.render(&TestColours, &users));
+        assert_eq!(expected, group.render(&TestColours, &users, UserFormat::Name));
+        assert_eq!(expected, group.render(&TestColours, &users, UserFormat::Numeric));
     }
 
     #[test]
@@ -86,7 +98,7 @@ pub mod test {
 
         let group = f::Group(100);
         let expected = TextCell::paint_str(Fixed(80).normal(), "folk");
-        assert_eq!(expected, group.render(&TestColours, &users))
+        assert_eq!(expected, group.render(&TestColours, &users, UserFormat::Name))
     }
 
     #[test]
@@ -99,13 +111,13 @@ pub mod test {
 
         let group = f::Group(100);
         let expected = TextCell::paint_str(Fixed(80).normal(), "folk");
-        assert_eq!(expected, group.render(&TestColours, &users))
+        assert_eq!(expected, group.render(&TestColours, &users, UserFormat::Name))
     }
 
     #[test]
     fn overflow() {
         let group = f::Group(2_147_483_648);
         let expected = TextCell::paint_str(Fixed(81).normal(), "2147483648");
-        assert_eq!(expected, group.render(&TestColours, &MockUsers::with_current_uid(0)));
+        assert_eq!(expected, group.render(&TestColours, &MockUsers::with_current_uid(0), UserFormat::Numeric));
     }
 }

+ 16 - 9
src/output/render/users.rs

@@ -3,13 +3,15 @@ use users::Users;
 
 use crate::fs::fields as f;
 use crate::output::cell::TextCell;
+use crate::output::table::UserFormat;
 
 
 impl f::User {
-    pub fn render<C: Colours, U: Users>(self, colours: &C, users: &U) -> TextCell {
-        let user_name = match users.get_user_by_uid(self.0) {
-            Some(user)  => user.name().to_string_lossy().into(),
-            None        => self.0.to_string(),
+    pub fn render<C: Colours, U: Users>(self, colours: &C, users: &U, format: UserFormat) -> TextCell {
+        let user_name = match (format, users.get_user_by_uid(self.0)) {
+            (_, None)                      => self.0.to_string(),
+            (UserFormat::Numeric, _)       => self.0.to_string(),
+            (UserFormat::Name, Some(user)) => user.name().to_string_lossy().into(),
         };
 
         let style = if users.get_current_uid() == self.0 { colours.you() }
@@ -31,6 +33,7 @@ pub mod test {
     use super::Colours;
     use crate::fs::fields as f;
     use crate::output::cell::TextCell;
+    use crate::output::table::UserFormat;
 
     use users::User;
     use users::mock::MockUsers;
@@ -53,7 +56,10 @@ pub mod test {
 
         let user = f::User(1000);
         let expected = TextCell::paint_str(Red.bold(), "enoch");
-        assert_eq!(expected, user.render(&TestColours, &users))
+        assert_eq!(expected, user.render(&TestColours, &users, UserFormat::Name));
+
+        let expected = TextCell::paint_str(Red.bold(), "1000");
+        assert_eq!(expected, user.render(&TestColours, &users, UserFormat::Numeric));
     }
 
     #[test]
@@ -62,7 +68,8 @@ pub mod test {
 
         let user = f::User(1000);
         let expected = TextCell::paint_str(Red.bold(), "1000");
-        assert_eq!(expected, user.render(&TestColours, &users));
+        assert_eq!(expected, user.render(&TestColours, &users, UserFormat::Name));
+        assert_eq!(expected, user.render(&TestColours, &users, UserFormat::Numeric));
     }
 
     #[test]
@@ -72,20 +79,20 @@ pub mod test {
 
         let user = f::User(1000);
         let expected = TextCell::paint_str(Blue.underline(), "enoch");
-        assert_eq!(expected, user.render(&TestColours, &users));
+        assert_eq!(expected, user.render(&TestColours, &users, UserFormat::Name));
     }
 
     #[test]
     fn different_unnamed() {
         let user = f::User(1000);
         let expected = TextCell::paint_str(Blue.underline(), "1000");
-        assert_eq!(expected, user.render(&TestColours, &MockUsers::with_current_uid(0)));
+        assert_eq!(expected, user.render(&TestColours, &MockUsers::with_current_uid(0), UserFormat::Numeric));
     }
 
     #[test]
     fn overflow() {
         let user = f::User(2_147_483_648);
         let expected = TextCell::paint_str(Blue.underline(), "2147483648");
-        assert_eq!(expected, user.render(&TestColours, &MockUsers::with_current_uid(0)));
+        assert_eq!(expected, user.render(&TestColours, &MockUsers::with_current_uid(0), UserFormat::Numeric));
     }
 }

+ 14 - 2
src/output/table.rs

@@ -23,6 +23,7 @@ use crate::theme::Theme;
 pub struct Options {
     pub size_format: SizeFormat,
     pub time_format: TimeFormat,
+    pub user_format: UserFormat,
     pub columns: Columns,
 }
 
@@ -180,6 +181,15 @@ pub enum SizeFormat {
     JustBytes,
 }
 
+/// Formatting options for user and group.
+#[derive(PartialEq, Debug, Copy, Clone)]
+pub enum UserFormat {
+    /// The UID / GID
+    Numeric,
+    /// Show the name
+    Name,
+}
+
 impl Default for SizeFormat {
     fn default() -> Self {
         Self::DecimalBytes
@@ -322,6 +332,7 @@ pub struct Table<'a> {
     widths: TableWidths,
     time_format: TimeFormat,
     size_format: SizeFormat,
+    user_format: UserFormat,
     git: Option<&'a GitCache>,
 }
 
@@ -344,6 +355,7 @@ impl<'a, 'f> Table<'a> {
             env,
             time_format: options.time_format,
             size_format: options.size_format,
+            user_format: options.user_format,
         }
     }
 
@@ -403,10 +415,10 @@ impl<'a, 'f> Table<'a> {
                 file.blocks().render(self.theme)
             }
             Column::User => {
-                file.user().render(self.theme, &*self.env.lock_users())
+                file.user().render(self.theme, &*self.env.lock_users(), self.user_format)
             }
             Column::Group => {
-                file.group().render(self.theme, &*self.env.lock_users())
+                file.group().render(self.theme, &*self.env.lock_users(), self.user_format)
             }
             Column::GitStatus => {
                 self.git_status(file).render(self.theme)