diff --git a/completions/completions.fish b/completions/completions.fish index 4126607..e540a57 100755 --- a/completions/completions.fish +++ b/completions/completions.fish @@ -65,6 +65,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-uid-gid' -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" diff --git a/completions/completions.zsh b/completions/completions.zsh index a700d47..fc28b4a 100644 --- a/completions/completions.zsh +++ b/completions/completions.zsh @@ -39,6 +39,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-uid-gid}"[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)" \ diff --git a/man/exa.1.md b/man/exa.1.md index a0a97de..5fedd80 100644 --- a/man/exa.1.md +++ b/man/exa.1.md @@ -140,6 +140,9 @@ These options are available when running with `--long` (`-l`): `-m`, `--modified` : Use the modified timestamp field. +`-n`, `--numeric-uid-gid` +: List numeric user and group IDs. + `-S`, `--blocks` : List each file’s number of file system blocks. diff --git a/src/options/flags.rs b/src/options/flags.rs index 88d53da..2b32b5a 100644 --- a/src/options/flags.rs +++ b/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 NUM_UGID: Arg = Arg { short: Some(b'n'), long: "numeric-uid-gid", 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 }; @@ -74,7 +75,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, &NUM_UGID, &HEADER, &ICONS, &INODE, &LINKS, &MODIFIED, &CHANGED, &BLOCKS, &TIME, &ACCESSED, &CREATED, &TIME_STYLE, &NO_PERMISSIONS, &NO_FILESIZE, &NO_USER, &NO_TIME, diff --git a/src/options/help.rs b/src/options/help.rs index d7b986a..7c091f8 100644 --- a/src/options/help.rs +++ b/src/options/help.rs @@ -46,6 +46,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-uid-gid 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 diff --git a/src/options/view.rs b/src/options/view.rs index 9ad42ae..96aaec6 100644 --- a/src/options/view.rs +++ b/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; @@ -183,8 +183,9 @@ impl TableOptions { fn deduce(matches: &MatchedFlags<'_>, vars: &V) -> Result { 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}) } } @@ -265,6 +266,14 @@ impl TimeFormat { } } +impl UserFormat { + fn deduce(matches: &MatchedFlags<'_>) -> Result { + let flag = matches.has(&flags::NUM_UGID)?; + Ok(if flag { Self::Numeric } else { Self::Name }) + } + +} + impl TimeTypes { diff --git a/src/output/render/groups.rs b/src/output/render/groups.rs index 1a1e812..ce33f9f 100644 --- a/src/output/render/groups.rs +++ b/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(self, colours: &C, users: &U) -> TextCell { + pub fn render(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)); } } diff --git a/src/output/render/users.rs b/src/output/render/users.rs index adda168..6a99a98 100644 --- a/src/output/render/users.rs +++ b/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(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(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)); } } diff --git a/src/output/table.rs b/src/output/table.rs index 50c7de7..be42082 100644 --- a/src/output/table.rs +++ b/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 @@ -311,6 +321,7 @@ pub struct Table<'a> { widths: TableWidths, time_format: TimeFormat, size_format: SizeFormat, + user_format: UserFormat, git: Option<&'a GitCache>, } @@ -333,6 +344,7 @@ impl<'a, 'f> Table<'a> { env, time_format: options.time_format, size_format: options.size_format, + user_format: options.user_format, } } @@ -392,10 +404,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)