Skip to content

Commit b97d2ef

Browse files
authored
Merge pull request #138 from seapagan/feat/permission-display-modes
Add permission display modes
2 parents 5e2a92b + 3585287 commit b97d2ef

17 files changed

Lines changed: 413 additions & 16 deletions

File tree

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ Currently, only a sub-set of the standard `ls` options are supported. These are:
114114
- `-F` / `--classify` - Append type indicators, including `*` for executables
115115
- `--no-indicators` - Disable file type indicators
116116
- `-l` / `--long` - Show long format listing
117+
- `--permissions <MODE>` - Select long-format permission display:
118+
`symbolic`, `octal`, `both`, or `none`
117119
- `-h` / `--human-readable` - Human readable file sizes using powers of 1024
118120
- `--si` - Human readable file sizes using powers of 1000
119121
- `-D` / `--sort-dirs` - Sort directories first
@@ -154,6 +156,11 @@ piped, and redirected output is plain by default. You can also disable styled
154156
output explicitly with `--no-color`, `no_color = true` in the config file, or
155157
the `NO_COLOR` environment variable.
156158

159+
Long-format output shows symbolic permissions by default. Use
160+
`--permissions octal` to replace them with octal permission bits,
161+
`--permissions both` to add octal bits after the symbolic field, or
162+
`--permissions none` to omit permission fields.
163+
157164
Long-format output colors permission bits, timestamp freshness, and large file
158165
sizes by default. You can adjust those accents independently with
159166
`--no-permission-colors`, `--no-time-gradient`, `--no-size-colors`, or the

docs/src/config.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,17 @@ This option controls long-format colors for the file type character and
187187
permission bits. Set it to `false`, or pass `--no-permission-colors`, to render
188188
those fields without accent colors.
189189

190+
### permissions
191+
192+
- Permitted values: `symbolic`, `octal`, `both`, or `none`
193+
- Default value: `symbolic`
194+
195+
This option corresponds to `--permissions` and controls long-format permission
196+
fields. `symbolic` shows the default file type character and symbolic
197+
permissions, `octal` replaces that field with the file type character and
198+
four-digit octal permission bits, `both` adds an octal permission cell after
199+
the symbolic field, and `none` omits permission fields.
200+
190201
### time_gradient
191202

192203
- Permitted values: `true` or `false`
@@ -245,6 +256,7 @@ human_readable = true
245256
# prune_dirs = ["target", "dist"]
246257
no_color = true
247258
permission_colors = false
259+
permissions = "symbolic"
248260
time_gradient = false
249261
size_colors = false
250262
fuzzy_time = true

docs/src/usage.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ Currently, only a sub-set of the standard `ls` options are supported. These are:
1818
- `-F` / `--classify` - Append type indicators, including `*` for executables
1919
- `--no-indicators` - Disable file type indicators
2020
- `-l` / `--long` - Show long format listing
21+
- `--permissions <MODE>` - Select long-format permission display:
22+
`symbolic`, `octal`, `both`, or `none`
2123
- `-h` / `--human-readable` - Human readable file sizes using powers of 1024
2224
- `--si` - Human readable file sizes using powers of 1000
2325
- `-R` / `--recursive` - List subdirectories recursively
@@ -95,6 +97,11 @@ piped, and redirected output is plain by default. You can also disable styled
9597
output explicitly with `--no-color`, `no_color = true` in the config file, or
9698
the `NO_COLOR` environment variable.
9799

100+
Long-format output shows symbolic permissions by default. Use
101+
`--permissions octal` to replace them with octal permission bits,
102+
`--permissions both` to add octal bits after the symbolic field, or
103+
`--permissions none` to omit permission fields.
104+
98105
Long-format output colors permission bits, timestamp freshness, and large file
99106
sizes by default. You can adjust those accents independently with
100107
`--no-permission-colors`, `--no-time-gradient`, `--no-size-colors`, or the

src/cli.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use clap::{Arg, ArgAction, ArgMatches, Command};
99
use std::env;
1010
use std::ffi::OsString;
1111

12-
use crate::IndicatorStyle;
12+
use crate::{IndicatorStyle, structs::PermissionDisplay};
1313

1414
const ARG_SHOW_ALL: &str = "show_all";
1515
const ARG_ALMOST_ALL: &str = "almost_all";
@@ -31,6 +31,7 @@ const ARG_DIRS_FIRST: &str = "dirs_first";
3131
const ARG_NO_ICONS: &str = "no_icons";
3232
const ARG_NO_COLOR: &str = "no_color";
3333
const ARG_NO_PERMISSION_COLORS: &str = "no_permission_colors";
34+
const ARG_PERMISSIONS: &str = "permissions";
3435
const ARG_NO_TIME_GRADIENT: &str = "no_time_gradient";
3536
const ARG_NO_SIZE_COLORS: &str = "no_size_colors";
3637
const ARG_GITIGNORE: &str = "gitignore";
@@ -100,6 +101,8 @@ pub struct Flags {
100101
pub no_color: bool,
101102
/// Disable permission and file-type colors in long-format output.
102103
pub no_permission_colors: bool,
104+
/// Override long-format permission display mode.
105+
pub permissions: Option<PermissionDisplay>,
103106
/// Use the fixed timestamp color instead of age-based colors.
104107
pub no_time_gradient: bool,
105108
/// Disable large-size colors in long-format output.
@@ -186,6 +189,7 @@ fn build_command(mode: CompatMode) -> Command {
186189
.arg(no_icons_arg())
187190
.arg(no_color_arg(mode))
188191
.arg(no_permission_colors_arg())
192+
.arg(permissions_arg())
189193
.arg(no_time_gradient_arg())
190194
.arg(no_size_colors_arg())
191195
.arg(gitignore_arg(mode))
@@ -418,6 +422,15 @@ fn no_permission_colors_arg() -> Arg {
418422
)
419423
}
420424

425+
fn permissions_arg() -> Arg {
426+
Arg::new(ARG_PERMISSIONS)
427+
.long("permissions")
428+
.action(ArgAction::Set)
429+
.value_name("MODE")
430+
.value_parser(clap::value_parser!(PermissionDisplay))
431+
.help("Select long-format permission display: symbolic, octal, both, or none")
432+
}
433+
421434
fn no_time_gradient_arg() -> Arg {
422435
Arg::new(ARG_NO_TIME_GRADIENT)
423436
.long("no-time-gradient")
@@ -493,6 +506,9 @@ fn flags_from_matches(mode: CompatMode, matches: &ArgMatches) -> Flags {
493506
no_icons: matches.get_flag(ARG_NO_ICONS),
494507
no_color: matches.get_flag(ARG_NO_COLOR),
495508
no_permission_colors: matches.get_flag(ARG_NO_PERMISSION_COLORS),
509+
permissions: matches
510+
.get_one::<PermissionDisplay>(ARG_PERMISSIONS)
511+
.copied(),
496512
no_time_gradient: matches.get_flag(ARG_NO_TIME_GRADIENT),
497513
no_size_colors: matches.get_flag(ARG_NO_SIZE_COLORS),
498514
gitignore: matches.get_flag(ARG_GITIGNORE),

src/structs.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use super::utils::icons::Icon;
2+
use clap::ValueEnum;
23
use config::Config;
34
use serde::Deserialize;
45
use std::convert::From;
@@ -29,6 +30,24 @@ pub enum IndicatorStyle {
2930
Classify,
3031
}
3132

33+
/// Long-format permission column display modes.
34+
#[derive(
35+
Debug, Clone, Copy, Deserialize, PartialEq, Eq, Default, ValueEnum,
36+
)]
37+
#[serde(rename_all = "kebab-case")]
38+
#[value(rename_all = "kebab-case")]
39+
pub enum PermissionDisplay {
40+
/// Show the existing file-type character plus symbolic permissions.
41+
#[default]
42+
Symbolic,
43+
/// Show the file-type character plus octal permission bits.
44+
Octal,
45+
/// Show symbolic permissions and a separate octal permission cell.
46+
Both,
47+
/// Hide permission cells entirely.
48+
None,
49+
}
50+
3251
/// Runtime options after CLI flags and config defaults have been merged.
3352
#[derive(Debug, PartialEq)]
3453
pub struct Params {
@@ -62,6 +81,8 @@ pub struct Params {
6281
pub no_color: bool,
6382
/// Color file type and permission bits in long-format output.
6483
pub permission_colors: bool,
84+
/// Select which permission fields to show in long-format output.
85+
pub permissions: PermissionDisplay,
6586
/// Color timestamps by age in long-format output.
6687
pub time_gradient: bool,
6788
/// Color large sizes in long-format output.
@@ -90,6 +111,7 @@ impl Default for Params {
90111
no_icons: false,
91112
no_color: false,
92113
permission_colors: true,
114+
permissions: PermissionDisplay::Symbolic,
93115
time_gradient: true,
94116
size_colors: true,
95117
gitignore: false,
@@ -116,6 +138,7 @@ pub(crate) struct RawParams {
116138
no_icons: bool,
117139
no_color: bool,
118140
permission_colors: Option<bool>,
141+
permissions: PermissionDisplay,
119142
time_gradient: Option<bool>,
120143
size_colors: Option<bool>,
121144
gitignore: bool,
@@ -182,6 +205,7 @@ impl From<RawParams> for Params {
182205
no_icons: raw.no_icons,
183206
no_color: raw.no_color,
184207
permission_colors: raw.permission_colors.unwrap_or(true),
208+
permissions: raw.permissions,
185209
time_gradient: raw.time_gradient.unwrap_or(true),
186210
size_colors: raw.size_colors.unwrap_or(true),
187211
gitignore: raw.gitignore,
@@ -223,6 +247,7 @@ impl Params {
223247
no_color: flags.no_color || config.no_color,
224248
permission_colors: config.permission_colors
225249
&& !flags.no_permission_colors,
250+
permissions: flags.permissions.unwrap_or(config.permissions),
226251
time_gradient: config.time_gradient && !flags.no_time_gradient,
227252
size_colors: config.size_colors && !flags.no_size_colors,
228253
gitignore: flags.gitignore || config.gitignore,
@@ -294,6 +319,9 @@ pub struct FileInfo {
294319
/// Unix-style permission string, possibly including `s`, `S`, `t`, or
295320
/// `T` special-bit overlays.
296321
pub mode: String,
322+
/// Unix permission bits masked to the displayable permission and special
323+
/// bit range.
324+
pub mode_bits: u32,
297325
/// Link count from filesystem metadata.
298326
pub nlink: u64,
299327
/// Owner name, or numeric user ID when lookup fails.

src/utils/file.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ use std::os::unix::ffi::OsStrExt;
2828
struct FileDetails {
2929
file_type: String,
3030
mode: String,
31+
mode_bits: u32,
3132
nlink: u64,
3233
size: u64,
3334
mtime: SystemTime,
@@ -112,6 +113,7 @@ fn get_file_details(metadata: &fs::Metadata) -> FileDetails {
112113
FileDetails {
113114
file_type,
114115
mode: rwx_mode,
116+
mode_bits: mode & 0o7777,
115117
nlink,
116118
size,
117119
mtime,
@@ -377,6 +379,7 @@ pub(crate) fn create_file_info_from_metadata_with_gitignore(
377379
FileInfo {
378380
file_type: details.file_type,
379381
mode: details.mode,
382+
mode_bits: details.mode_bits,
380383
nlink: details.nlink,
381384
user: details.user,
382385
group: details.group,

src/utils/format.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ pub fn mode_to_rwx(mode: u32) -> String {
1919
rwx
2020
}
2121

22+
/// Format Unix permission and special bits as four octal digits.
23+
pub fn mode_to_octal(mode: u32) -> String {
24+
format!("{:04o}", mode & 0o7777)
25+
}
26+
2227
fn permission_char(mode: u32, bit: u32, value: char) -> char {
2328
if mode & bit != 0 { value } else { '-' }
2429
}

src/utils/render.rs

Lines changed: 67 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@ use strip_ansi_escapes::strip_str;
1515
use terminal_size::{Height, Width, terminal_size};
1616
use unicode_width::UnicodeWidthStr;
1717

18-
use crate::Params;
1918
use crate::structs::{FileInfo, NameStyle};
2019
use crate::utils;
2120
use crate::utils::color::{LongFormatColorLevel, long_format_color_level};
2221
use crate::utils::file::check_display_name;
2322
use crate::utils::time::{DAY, MONTH, WEEK, YEAR};
23+
use crate::{Params, structs::PermissionDisplay};
2424

2525
const SHORT_CELL_PADDING: usize = 2;
2626
const LARGE_SIZE_BYTES: u64 = 1024 * 1024;
@@ -79,12 +79,9 @@ fn build_long_format_table_with_name_prefixes<'a>(
7979
let (display_size, units) =
8080
utils::format::show_size(info.size, size_scale);
8181

82-
let mut row_cells = Vec::with_capacity(9);
82+
let mut row_cells = Vec::with_capacity(10);
8383

84-
row_cells.push(Cell::new(&format!(
85-
"{} ",
86-
long_permission_text(info, params)
87-
)));
84+
append_permission_cells(&mut row_cells, info, params, color_level);
8885
row_cells.push(Cell::new(&info.nlink.to_string()));
8986
row_cells.push(Cell::new(&format!(" {}", info.user.cyan())));
9087
row_cells.push(Cell::new(&format!("{} ", info.group.green())));
@@ -127,16 +124,76 @@ fn build_long_format_table_with_name_prefixes<'a>(
127124
table
128125
}
129126

130-
fn long_permission_text(info: &FileInfo, params: &Params) -> String {
127+
fn append_permission_cells(
128+
row_cells: &mut Vec<Cell>,
129+
info: &FileInfo,
130+
params: &Params,
131+
color_level: LongFormatColorLevel,
132+
) {
133+
match params.permissions {
134+
PermissionDisplay::Symbolic => {
135+
row_cells.push(Cell::new(&format!(
136+
"{} ",
137+
long_permission_text(info, params)
138+
)));
139+
}
140+
PermissionDisplay::Octal => {
141+
row_cells.push(Cell::new(&format!(
142+
"{} {} ",
143+
long_file_type_text(info, params),
144+
long_octal_permission_text(info, params, color_level)
145+
)));
146+
}
147+
PermissionDisplay::Both => {
148+
row_cells.push(Cell::new(&long_permission_text(info, params)));
149+
row_cells.push(Cell::new(&format!(
150+
"{} ",
151+
long_octal_permission_text(info, params, color_level)
152+
)));
153+
}
154+
PermissionDisplay::None => {}
155+
}
156+
}
157+
158+
fn long_file_type_text(info: &FileInfo, params: &Params) -> String {
131159
if !params.permission_colors {
132-
return format!("{}{}", info.file_type, info.mode);
160+
return info.file_type.clone();
133161
}
134162

135-
let mut output =
136-
String::with_capacity(info.file_type.len() + info.mode.len());
163+
let mut output = String::with_capacity(info.file_type.len());
137164
for value in info.file_type.chars() {
138165
write_file_type_char(&mut output, value);
139166
}
167+
output
168+
}
169+
170+
fn long_octal_permission_text(
171+
info: &FileInfo,
172+
params: &Params,
173+
color_level: LongFormatColorLevel,
174+
) -> String {
175+
let text = utils::format::mode_to_octal(info.mode_bits);
176+
if !params.permission_colors {
177+
return text;
178+
}
179+
180+
match color_level {
181+
LongFormatColorLevel::Truecolor => text.rgb(238, 204, 92).to_string(),
182+
LongFormatColorLevel::Ansi256 => {
183+
format!("\x1b[38;5;221m{text}\x1b[0m")
184+
}
185+
LongFormatColorLevel::Named => text.yellow().dim().to_string(),
186+
LongFormatColorLevel::None => text,
187+
}
188+
}
189+
190+
fn long_permission_text(info: &FileInfo, params: &Params) -> String {
191+
if !params.permission_colors {
192+
return format!("{}{}", info.file_type, info.mode);
193+
}
194+
195+
let mut output = long_file_type_text(info, params);
196+
output.reserve(info.mode.len());
140197
for value in info.mode.chars() {
141198
write_permission_char(&mut output, value);
142199
}

tests/crate/app.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ fn test_run_with_flags_lists_matching_entries() {
9797
no_icons: true,
9898
no_color: false,
9999
no_permission_colors: false,
100+
permissions: None,
100101
no_time_gradient: false,
101102
no_size_colors: false,
102103
gitignore: false,

0 commit comments

Comments
 (0)