Add --permission octal to show permissions in octal

This commit is contained in:
Abin Simon 2022-03-01 08:58:49 +05:30 committed by Abin Simon
parent 019e8e424f
commit 8dd54fee5e
11 changed files with 390 additions and 31 deletions

View file

@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Add support for `--no-sort` `-U` from [MichaelAug](https://github.com/MichaelAug)
- Add `--group-directories-first` as an alias for `--group-dirs=first` to improve compatibility with `coreutils/ls`
- Add `--permission` flag to choose permission formatting (rwx, octal) from [meain](https://github.com/meain)
### Fixed
- Support non-bold bright colors [#248](https://github.com/Peltoche/lsd/issues/248) from [meain](https://github.com/meain)
- Don't automatically dereference symlinks in tree/recursive [#637](https://github.com/Peltoche/lsd/issues/637) from [meain](https://github.com/meain)

View file

@ -179,6 +179,11 @@ recursion:
# Possible values: default, short, bytes
size: default
# == Permission ==
# Specify the format of the permission column
# Possible value: rwx, octal
permission: rwx
# == Sorting ==
sorting:
# Specify what to sort by.
@ -246,6 +251,7 @@ permission:
exec: dark_red
exec-sticky: 5
no-access: 245
octal: 6
date:
hour-old: 40
day-old: 42

View file

@ -116,6 +116,9 @@ lsd is a ls command with a lot of pretty colours and some other stuff to enrich
`-I, --ignore-glob <pattern>...`
: Do not display files/directories with names matching the glob pattern(s). More than one can be specified by repeating the argument [default: ]
* [ ] `--permission <permission>...`
: How to display permissions [default: rwx] [possible values: rwx, octal]
`--size <size>...`
: How to display size [default: default] [possible values: default, short, bytes]

View file

@ -124,6 +124,16 @@ pub fn build() -> App<'static, 'static> {
.conflicts_with("recursive")
.help("Display directories themselves, and not their contents (recursively when used with --tree)"),
)
.arg(
Arg::with_name("permission")
.long("permission")
.default_value("rwx")
.possible_value("rwx")
.possible_value("octal")
.multiple(true)
.number_of_values(1)
.help("How to display permissions"),
)
.arg(
Arg::with_name("size")
.long("size")

View file

@ -35,6 +35,7 @@ pub enum Elem {
Exec,
ExecSticky,
NoAccess,
Octal,
/// Last Time Modified
DayOld,
@ -102,6 +103,7 @@ impl Elem {
Elem::Exec => theme.permission.exec,
Elem::ExecSticky => theme.permission.exec_sticky,
Elem::NoAccess => theme.permission.no_access,
Elem::Octal => theme.permission.octal,
Elem::DayOld => theme.date.day_old,
Elem::HourOld => theme.date.hour_old,
@ -340,6 +342,7 @@ mod elem {
exec: Color::Red,
exec_sticky: Color::Magenta,
no_access: Color::AnsiValue(245), // Grey
octal: Color::AnsiValue(6),
},
file_type: theme::FileType {
file: theme::File {

View file

@ -38,6 +38,7 @@ pub struct Permission {
pub exec: Color,
pub exec_sticky: Color,
pub no_access: Color,
pub octal: Color,
}
#[derive(Debug, Deserialize, PartialEq)]
@ -132,6 +133,7 @@ impl Default for Permission {
exec: Color::DarkRed,
exec_sticky: Color::AnsiValue(5),
no_access: Color::AnsiValue(245), // Grey
octal: Color::AnsiValue(6),
}
}
}

View file

@ -1,6 +1,7 @@
use crate::flags::display::Display;
use crate::flags::icons::{IconOption, IconTheme};
use crate::flags::layout::Layout;
use crate::flags::permission::PermissionFlag;
use crate::flags::size::SizeFlag;
use crate::flags::sorting::{DirGrouping, SortColumn};
use crate::flags::{ColorOption, ThemeOption};
@ -36,6 +37,7 @@ pub struct Config {
pub layout: Option<Layout>,
pub recursion: Option<Recursion>,
pub size: Option<SizeFlag>,
pub permission: Option<PermissionFlag>,
pub sorting: Option<Sorting>,
pub no_symlink: Option<bool>,
pub total_size: Option<bool>,
@ -85,6 +87,7 @@ impl Config {
layout: None,
recursion: None,
size: None,
permission: None,
sorting: None,
no_symlink: None,
total_size: None,
@ -278,6 +281,11 @@ recursion:
# Possible values: default, short, bytes
size: default
# == Permission ==
# Specify the format of the permission column.
# Possible value: rwx, octal
permission: rwx
# == Sorting ==
sorting:
# Specify what to sort by.
@ -320,6 +328,7 @@ mod tests {
use crate::flags::color::{ColorOption, ThemeOption};
use crate::flags::icons::{IconOption, IconTheme};
use crate::flags::layout::Layout;
use crate::flags::permission::PermissionFlag;
use crate::flags::size::SizeFlag;
use crate::flags::sorting::{DirGrouping, SortColumn};
@ -360,6 +369,7 @@ mod tests {
depth: None,
}),
size: Some(SizeFlag::Default),
permission: Some(PermissionFlag::Rwx),
sorting: Some(config_file::Sorting {
column: Some(SortColumn::Name),
reverse: Some(false),

View file

@ -270,7 +270,7 @@ fn get_output<'a>(
Block::Permission => {
block_vec.extend(vec![
meta.file_type.render(colors),
meta.permissions.render(colors),
meta.permissions.render(colors, flags),
]);
}
Block::User => block_vec.push(meta.owner.render_user(colors)),

View file

@ -7,6 +7,7 @@ pub mod icons;
pub mod ignore_globs;
pub mod indicators;
pub mod layout;
pub mod permission;
pub mod recursion;
pub mod size;
pub mod sorting;
@ -28,6 +29,7 @@ pub use icons::Icons;
pub use ignore_globs::IgnoreGlobs;
pub use indicators::Indicators;
pub use layout::Layout;
pub use permission::PermissionFlag;
pub use recursion::Recursion;
pub use size::SizeFlag;
pub use sorting::DirGrouping;
@ -60,6 +62,7 @@ pub struct Flags {
pub no_symlink: NoSymlink,
pub recursion: Recursion,
pub size: SizeFlag,
pub permission: PermissionFlag,
pub sorting: Sorting,
pub total_size: TotalSize,
pub symlink_arrow: SymlinkArrow,
@ -81,6 +84,7 @@ impl Flags {
display: Display::configure_from(matches, config),
layout: Layout::configure_from(matches, config),
size: SizeFlag::configure_from(matches, config),
permission: PermissionFlag::configure_from(matches, config),
display_indicators: Indicators::configure_from(matches, config),
icons: Icons::configure_from(matches, config),
ignore_globs: IgnoreGlobs::configure_from(matches, config)?,

167
src/flags/permission.rs Normal file
View file

@ -0,0 +1,167 @@
//! This module defines the [PermissionFlag]. To set it up from [ArgMatches], a [Config] and its
//! [Default] value, use its [configure_from](Configurable::configure_from) method.
use super::Configurable;
use crate::config_file::Config;
use clap::ArgMatches;
use serde::Deserialize;
/// The flag showing which file permissions units to use.
#[derive(Clone, Debug, Copy, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum PermissionFlag {
/// The variant to show file permissions in rwx format
Rwx,
/// The variant to show file permissions in octal format
Octal,
}
impl PermissionFlag {
fn from_str(value: &str) -> Option<Self> {
match value {
"rwx" => Some(Self::Rwx),
"octal" => Some(Self::Octal),
_ => {
panic!(
"Permissions can only be one of rwx or octal, but got {}.",
value
);
}
}
}
}
impl Configurable<Self> for PermissionFlag {
/// Get a potential `PermissionFlag` variant from [ArgMatches].
///
/// If any of the "rwx" or "octal" arguments is passed, the corresponding
/// `PermissionFlag` variant is returned in a [Some]. If neither of them is passed,
/// this returns [None].
/// Sets permissions to rwx if classic flag is enabled.
fn from_arg_matches(matches: &ArgMatches) -> Option<Self> {
if matches.is_present("classic") {
return Some(Self::Rwx);
} else if matches.occurrences_of("permission") > 0 {
if let Some(permissions) = matches.values_of("permission")?.last() {
return Self::from_str(permissions);
}
}
None
}
/// Get a potential `PermissionFlag` variant from a [Config].
///
/// If the `Config::permissions` has value and is one of "rwx" or "octal",
/// this returns the corresponding `PermissionFlag` variant in a [Some].
/// Otherwise this returns [None].
/// Sets permissions to rwx if classic flag is enabled.
fn from_config(config: &Config) -> Option<Self> {
if let Some(true) = config.classic {
Some(Self::Rwx)
} else {
config.permission
}
}
}
/// The default value for `PermissionFlag` is [PermissionFlag::Default].
impl Default for PermissionFlag {
fn default() -> Self {
Self::Rwx
}
}
#[cfg(test)]
mod test {
use super::PermissionFlag;
use crate::app;
use crate::config_file::Config;
use crate::flags::Configurable;
#[test]
fn test_default() {
assert_eq!(PermissionFlag::Rwx, PermissionFlag::default());
}
#[test]
fn test_from_arg_matches_none() {
let argv = vec!["lsd"];
let matches = app::build().get_matches_from_safe(argv).unwrap();
assert_eq!(None, PermissionFlag::from_arg_matches(&matches));
}
#[test]
fn test_from_arg_matches_default() {
let argv = vec!["lsd", "--permission", "rwx"];
let matches = app::build().get_matches_from_safe(argv).unwrap();
assert_eq!(
Some(PermissionFlag::Rwx),
PermissionFlag::from_arg_matches(&matches)
);
}
#[test]
fn test_from_arg_matches_short() {
let args = vec!["lsd", "--permission", "octal"];
let matches = app::build().get_matches_from_safe(args).unwrap();
assert_eq!(
Some(PermissionFlag::Octal),
PermissionFlag::from_arg_matches(&matches)
);
}
#[test]
#[should_panic]
fn test_from_arg_matches_unknown() {
let args = vec!["lsd", "--permission", "unknown"];
let _ = app::build().get_matches_from_safe(args).unwrap();
}
#[test]
fn test_from_arg_matches_permissions_multi() {
let args = vec!["lsd", "--permission", "octal", "--permission", "rwx"];
let matches = app::build().get_matches_from_safe(args).unwrap();
assert_eq!(
Some(PermissionFlag::Rwx),
PermissionFlag::from_arg_matches(&matches)
);
}
#[test]
fn test_from_arg_matches_permissions_classic() {
let args = vec!["lsd", "--permission", "rwx", "--classic"];
let matches = app::build().get_matches_from_safe(args).unwrap();
assert_eq!(
Some(PermissionFlag::Rwx),
PermissionFlag::from_arg_matches(&matches)
);
}
#[test]
fn test_from_config_none() {
assert_eq!(None, PermissionFlag::from_config(&Config::with_none()));
}
#[test]
fn test_from_config_rwx() {
let mut c = Config::with_none();
c.permission = Some(PermissionFlag::Rwx);
assert_eq!(Some(PermissionFlag::Rwx), PermissionFlag::from_config(&c));
}
#[test]
fn test_from_config_octal() {
let mut c = Config::with_none();
c.permission = Some(PermissionFlag::Octal);
assert_eq!(Some(PermissionFlag::Octal), PermissionFlag::from_config(&c));
}
#[test]
fn test_from_config_classic_mode() {
let mut c = Config::with_none();
c.classic = Some(true);
assert_eq!(Some(PermissionFlag::Rwx), PermissionFlag::from_config(&c));
}
}

View file

@ -1,4 +1,5 @@
use crate::color::{ColoredString, Colors, Elem};
use crate::flags::{Flags, PermissionFlag};
use std::fs::Metadata;
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
@ -54,7 +55,11 @@ impl<'a> From<&'a Metadata> for Permissions {
}
impl Permissions {
pub fn render(&self, colors: &Colors) -> ColoredString {
fn bits_to_octal(r: bool, w: bool, x: bool) -> u8 {
(r as u8) * 4 + (w as u8) * 2 + (x as u8)
}
pub fn render(&self, colors: &Colors, flags: &Flags) -> ColoredString {
let bit = |bit, chr: &'static str, elem: &Elem| {
if bit {
colors.colorize(String::from(chr), elem)
@ -63,35 +68,53 @@ impl Permissions {
}
};
let strings: &[ColoredString] = &[
// User permissions
bit(self.user_read, "r", &Elem::Read),
bit(self.user_write, "w", &Elem::Write),
match (self.user_execute, self.setuid) {
(false, false) => colors.colorize(String::from("-"), &Elem::NoAccess),
(true, false) => colors.colorize(String::from("x"), &Elem::Exec),
(false, true) => colors.colorize(String::from("S"), &Elem::ExecSticky),
(true, true) => colors.colorize(String::from("s"), &Elem::ExecSticky),
},
// Group permissions
bit(self.group_read, "r", &Elem::Read),
bit(self.group_write, "w", &Elem::Write),
match (self.group_execute, self.setgid) {
(false, false) => colors.colorize(String::from("-"), &Elem::NoAccess),
(true, false) => colors.colorize(String::from("x"), &Elem::Exec),
(false, true) => colors.colorize(String::from("S"), &Elem::ExecSticky),
(true, true) => colors.colorize(String::from("s"), &Elem::ExecSticky),
},
// Other permissions
bit(self.other_read, "r", &Elem::Read),
bit(self.other_write, "w", &Elem::Write),
match (self.other_execute, self.sticky) {
(false, false) => colors.colorize(String::from("-"), &Elem::NoAccess),
(true, false) => colors.colorize(String::from("x"), &Elem::Exec),
(false, true) => colors.colorize(String::from("T"), &Elem::ExecSticky),
(true, true) => colors.colorize(String::from("t"), &Elem::ExecSticky),
},
];
let strings = match flags.permission {
PermissionFlag::Rwx => vec![
// User permissions
bit(self.user_read, "r", &Elem::Read),
bit(self.user_write, "w", &Elem::Write),
match (self.user_execute, self.setuid) {
(false, false) => colors.colorize(String::from("-"), &Elem::NoAccess),
(true, false) => colors.colorize(String::from("x"), &Elem::Exec),
(false, true) => colors.colorize(String::from("S"), &Elem::ExecSticky),
(true, true) => colors.colorize(String::from("s"), &Elem::ExecSticky),
},
// Group permissions
bit(self.group_read, "r", &Elem::Read),
bit(self.group_write, "w", &Elem::Write),
match (self.group_execute, self.setgid) {
(false, false) => colors.colorize(String::from("-"), &Elem::NoAccess),
(true, false) => colors.colorize(String::from("x"), &Elem::Exec),
(false, true) => colors.colorize(String::from("S"), &Elem::ExecSticky),
(true, true) => colors.colorize(String::from("s"), &Elem::ExecSticky),
},
// Other permissions
bit(self.other_read, "r", &Elem::Read),
bit(self.other_write, "w", &Elem::Write),
match (self.other_execute, self.sticky) {
(false, false) => colors.colorize(String::from("-"), &Elem::NoAccess),
(true, false) => colors.colorize(String::from("x"), &Elem::Exec),
(false, true) => colors.colorize(String::from("T"), &Elem::ExecSticky),
(true, true) => colors.colorize(String::from("t"), &Elem::ExecSticky),
},
],
PermissionFlag::Octal => {
let octal_sticky = Self::bits_to_octal(self.setuid, self.setgid, self.sticky);
let octal_user =
Self::bits_to_octal(self.user_read, self.user_write, self.user_execute);
let octal_group =
Self::bits_to_octal(self.group_read, self.group_write, self.group_execute);
let octal_other =
Self::bits_to_octal(self.other_read, self.other_write, self.other_execute);
vec![colors.colorize(
format!(
"{}{}{}{}",
octal_sticky, octal_user, octal_group, octal_other
),
&Elem::Octal,
)]
}
};
let res = strings
.iter()
@ -130,3 +153,133 @@ mod modes {
pub const SETGID: Mode = libc::S_ISGID as Mode;
pub const SETUID: Mode = libc::S_ISUID as Mode;
}
#[cfg(unix)]
#[cfg(test)]
mod test {
use super::Flags;
use super::{PermissionFlag, Permissions};
use crate::color::{Colors, ThemeOption};
use std::fs;
use std::fs::File;
use std::os::unix::fs::PermissionsExt;
use tempfile::tempdir;
#[test]
pub fn permission_rwx() {
let tmp_dir = tempdir().expect("failed to create temp dir");
// Create the file;
let file_path = tmp_dir.path().join("file.txt");
File::create(&file_path).expect("failed to create file");
fs::set_permissions(&file_path, fs::Permissions::from_mode(0o655))
.expect("unable to set permissions to file");
let meta = file_path.metadata().expect("failed to get meta");
let colors = Colors::new(ThemeOption::NoColor);
let perms = Permissions::from(&meta);
assert_eq!(
"rw-r-xr-x",
perms.render(&colors, &Flags::default()).content()
);
}
#[test]
pub fn permission_rwx2() {
let tmp_dir = tempdir().expect("failed to create temp dir");
// Create the file;
let file_path = tmp_dir.path().join("file.txt");
File::create(&file_path).expect("failed to create file");
fs::set_permissions(&file_path, fs::Permissions::from_mode(0o777))
.expect("unable to set permissions to file");
let meta = file_path.metadata().expect("failed to get meta");
let colors = Colors::new(ThemeOption::NoColor);
let perms = Permissions::from(&meta);
assert_eq!(
"rwxrwxrwx",
perms.render(&colors, &Flags::default()).content()
);
}
#[test]
pub fn permission_rwx_sticky() {
let tmp_dir = tempdir().expect("failed to create temp dir");
// Create the file;
let file_path = tmp_dir.path().join("file.txt");
File::create(&file_path).expect("failed to create file");
fs::set_permissions(&file_path, fs::Permissions::from_mode(0o1777))
.expect("unable to set permissions to file");
let meta = file_path.metadata().expect("failed to get meta");
let colors = Colors::new(ThemeOption::NoColor);
let mut flags = Flags::default();
flags.permission = PermissionFlag::Rwx;
let perms = Permissions::from(&meta);
assert_eq!("rwxrwxrwt", perms.render(&colors, &flags).content());
}
#[test]
pub fn permission_octal() {
let tmp_dir = tempdir().expect("failed to create temp dir");
// Create the file;
let file_path = tmp_dir.path().join("file.txt");
File::create(&file_path).expect("failed to create file");
fs::set_permissions(&file_path, fs::Permissions::from_mode(0o655))
.expect("unable to set permissions to file");
let meta = file_path.metadata().expect("failed to get meta");
let colors = Colors::new(ThemeOption::NoColor);
let mut flags = Flags::default();
flags.permission = PermissionFlag::Octal;
let perms = Permissions::from(&meta);
assert_eq!("0655", perms.render(&colors, &flags).content());
}
#[test]
pub fn permission_octal2() {
let tmp_dir = tempdir().expect("failed to create temp dir");
// Create the file;
let file_path = tmp_dir.path().join("file.txt");
File::create(&file_path).expect("failed to create file");
fs::set_permissions(&file_path, fs::Permissions::from_mode(0o777))
.expect("unable to set permissions to file");
let meta = file_path.metadata().expect("failed to get meta");
let colors = Colors::new(ThemeOption::NoColor);
let mut flags = Flags::default();
flags.permission = PermissionFlag::Octal;
let perms = Permissions::from(&meta);
assert_eq!("0777", perms.render(&colors, &flags).content());
}
#[test]
pub fn permission_octal_sticky() {
let tmp_dir = tempdir().expect("failed to create temp dir");
// Create the file;
let file_path = tmp_dir.path().join("file.txt");
File::create(&file_path).expect("failed to create file");
fs::set_permissions(&file_path, fs::Permissions::from_mode(0o1777))
.expect("unable to set permissions to file");
let meta = file_path.metadata().expect("failed to get meta");
let colors = Colors::new(ThemeOption::NoColor);
let mut flags = Flags::default();
flags.permission = PermissionFlag::Octal;
let perms = Permissions::from(&meta);
assert_eq!("1777", perms.render(&colors, &flags).content());
}
}