mirror of
https://github.com/lsd-rs/lsd
synced 2024-12-13 13:42:34 +00:00
Add configuration and CLI options: truncate owner
This adds the following CLI flags: - `--truncate-owner-after` - `--truncate-owner-marker` And the following configuration fields: ```yaml truncate-owner: after: marker: "" ``` The default behavior of LSD is unchanged. The problem this change attempts to solve is the usability of the `-l` flag on systems where some user or group names are long but cannot be changed (e.g. the user is not admin and the account is managed in a central directory). In such cases, even with a decently sized terminal (90+ characters wide), lines often overflow, making the directory listing hard to read. Without this change, the only mitigation would consist in turning off the display of file ownership (via the `blocks` configuration field) which is unsatisfactory because ownership information is very useful.
This commit is contained in:
parent
762e724f2a
commit
93b3fb0b66
10 changed files with 262 additions and 8 deletions
|
@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
- Add CLI parameters `--truncate-owner-after` and `--truncate-owner-marker` (and equivalent
|
||||
configuration fields) to truncate user and group names if they exceed a certain number
|
||||
of characters (disabled by default).
|
||||
|
||||
## [v1.0.0] - 2023-08-25
|
||||
|
||||
### Added
|
||||
|
@ -391,7 +398,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Change the component alignment by using term_grid
|
||||
|
||||
|
||||
|
||||
[Unreleased]: https://github.com/lsd-rs/lsd/compare/v1.0.0...HEAD
|
||||
[v1.0.0]: https://github.com/lsd-rs/lsd/compare/0.23.1...v1.0.0
|
||||
[0.23.1]: https://github.com/Peltoche/lsd/compare/0.23.0...0.23.1
|
||||
[0.23.0]: https://github.com/Peltoche/lsd/compare/0.22.0...0.23.0
|
||||
|
|
|
@ -228,6 +228,15 @@ symlink-arrow: ⇒
|
|||
# Whether to display block headers.
|
||||
# Possible values: false, true
|
||||
header: false
|
||||
|
||||
# == Truncate owner ==
|
||||
# How to truncate the username and group names for a file if they exceed a certain
|
||||
# number of characters.
|
||||
truncate-owner:
|
||||
# Number of characters to keep. By default, no truncation is done (empty value).
|
||||
after:
|
||||
# String to be appended to a name if truncated.
|
||||
marker: ""
|
||||
```
|
||||
|
||||
</details>
|
||||
|
|
|
@ -140,6 +140,12 @@ lsd is a ls command with a lot of pretty colours and some other stuff to enrich
|
|||
`--header`
|
||||
: Display block headers
|
||||
|
||||
`--truncate-owner-after`
|
||||
: Truncate the user and group names if they exceed a certain number of characters
|
||||
|
||||
`--truncate-owner-marker`
|
||||
: Truncation marker appended to a truncated user or group name
|
||||
|
||||
# ARGS
|
||||
|
||||
`<FILE>...`
|
||||
|
|
|
@ -176,6 +176,14 @@ pub struct Cli {
|
|||
#[arg(long)]
|
||||
pub header: bool,
|
||||
|
||||
/// Truncate the user and group names if they exceed a certain number of characters
|
||||
#[arg(long, value_name = "NUM")]
|
||||
pub truncate_owner_after: Option<usize>,
|
||||
|
||||
/// Truncation marker appended to a truncated user or group name
|
||||
#[arg(long, value_name = "STR")]
|
||||
pub truncate_owner_marker: Option<String>,
|
||||
|
||||
/// Includes files with the windows system protection flag set.
|
||||
/// This is the same as --all on other platforms
|
||||
#[arg(long, hide = !cfg!(windows))]
|
||||
|
|
|
@ -45,6 +45,7 @@ pub struct Config {
|
|||
pub symlink_arrow: Option<String>,
|
||||
pub hyperlink: Option<HyperlinkOption>,
|
||||
pub header: Option<bool>,
|
||||
pub truncate_owner: Option<TruncateOwner>,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Debug, Deserialize)]
|
||||
|
@ -74,6 +75,12 @@ pub struct Sorting {
|
|||
pub dir_grouping: Option<DirGrouping>,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Debug, Deserialize)]
|
||||
pub struct TruncateOwner {
|
||||
pub after: Option<usize>,
|
||||
pub marker: Option<String>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
/// This constructs a Config struct with all None
|
||||
pub fn with_none() -> Self {
|
||||
|
@ -97,6 +104,7 @@ impl Config {
|
|||
symlink_arrow: None,
|
||||
hyperlink: None,
|
||||
header: None,
|
||||
truncate_owner: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -323,6 +331,15 @@ hyperlink: never
|
|||
# == Symlink arrow ==
|
||||
# Specifies how the symlink arrow display, chars in both ascii and utf8
|
||||
symlink-arrow: ⇒
|
||||
|
||||
# == Truncate owner ==
|
||||
# How to truncate the username and group name for the file if they exceed a
|
||||
# certain number of characters.
|
||||
truncate-owner:
|
||||
# Number of characters to keep. By default, no truncation is done (empty value).
|
||||
after:
|
||||
# String to be appended to a name if truncated.
|
||||
marker: ""
|
||||
"#;
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -389,6 +406,10 @@ mod tests {
|
|||
symlink_arrow: Some("⇒".into()),
|
||||
hyperlink: Some(HyperlinkOption::Never),
|
||||
header: None,
|
||||
truncate_owner: Some(config_file::TruncateOwner {
|
||||
after: None,
|
||||
marker: Some("".to_string()),
|
||||
}),
|
||||
},
|
||||
c
|
||||
);
|
||||
|
|
|
@ -355,11 +355,11 @@ fn get_output(
|
|||
]);
|
||||
}
|
||||
Block::User => block_vec.push(match &meta.owner {
|
||||
Some(owner) => owner.render_user(colors),
|
||||
Some(owner) => owner.render_user(colors, flags),
|
||||
None => colorize_missing("?"),
|
||||
}),
|
||||
Block::Group => block_vec.push(match &meta.owner {
|
||||
Some(owner) => owner.render_group(colors),
|
||||
Some(owner) => owner.render_group(colors, flags),
|
||||
None => colorize_missing("?"),
|
||||
}),
|
||||
Block::Context => block_vec.push(match &meta.access_control {
|
||||
|
|
|
@ -16,6 +16,7 @@ pub mod sorting;
|
|||
pub mod symlink_arrow;
|
||||
pub mod symlinks;
|
||||
pub mod total_size;
|
||||
pub mod truncate_owner;
|
||||
|
||||
pub use blocks::Blocks;
|
||||
pub use color::Color;
|
||||
|
@ -42,6 +43,7 @@ pub use sorting::Sorting;
|
|||
pub use symlink_arrow::SymlinkArrow;
|
||||
pub use symlinks::NoSymlink;
|
||||
pub use total_size::TotalSize;
|
||||
pub use truncate_owner::TruncateOwner;
|
||||
|
||||
use crate::app::Cli;
|
||||
use crate::config_file::Config;
|
||||
|
@ -72,6 +74,7 @@ pub struct Flags {
|
|||
pub symlink_arrow: SymlinkArrow,
|
||||
pub hyperlink: HyperlinkOption,
|
||||
pub header: Header,
|
||||
pub truncate_owner: TruncateOwner,
|
||||
pub should_quote: bool,
|
||||
}
|
||||
|
||||
|
@ -102,6 +105,7 @@ impl Flags {
|
|||
symlink_arrow: SymlinkArrow::configure_from(cli, config),
|
||||
hyperlink: HyperlinkOption::configure_from(cli, config),
|
||||
header: Header::configure_from(cli, config),
|
||||
truncate_owner: TruncateOwner::configure_from(cli, config),
|
||||
should_quote: true,
|
||||
})
|
||||
}
|
||||
|
|
120
src/flags/truncate_owner.rs
Normal file
120
src/flags/truncate_owner.rs
Normal file
|
@ -0,0 +1,120 @@
|
|||
//! This module defines the [TruncateOwner] flag. To set it up from [Cli], a [Config] and its
|
||||
//! [Default] value, use the [configure_from](Configurable::configure_from) method.
|
||||
|
||||
use super::Configurable;
|
||||
use crate::app::Cli;
|
||||
|
||||
use crate::config_file::Config;
|
||||
|
||||
/// The flag showing how to truncate user and group names.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Default)]
|
||||
pub struct TruncateOwner {
|
||||
pub after: Option<usize>,
|
||||
pub marker: Option<String>,
|
||||
}
|
||||
|
||||
impl Configurable<Self> for TruncateOwner {
|
||||
/// Get a potential `TruncateOwner` value from [Cli].
|
||||
///
|
||||
/// If the "header" argument is passed, this returns a `TruncateOwner` with value `true` in a
|
||||
/// [Some]. Otherwise this returns [None].
|
||||
fn from_cli(cli: &Cli) -> Option<Self> {
|
||||
match (cli.truncate_owner_after, cli.truncate_owner_marker.clone()) {
|
||||
(None, None) => None,
|
||||
(after, marker) => Some(Self { after, marker }),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a potential `TruncateOwner` value from a [Config].
|
||||
///
|
||||
/// If the `Config::truncate_owner` has value,
|
||||
/// this returns it as the value of the `TruncateOwner`, in a [Some].
|
||||
/// Otherwise this returns [None].
|
||||
fn from_config(config: &Config) -> Option<Self> {
|
||||
config.truncate_owner.as_ref().map(|c| Self {
|
||||
after: c.after,
|
||||
marker: c.marker.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use clap::Parser;
|
||||
|
||||
use super::TruncateOwner;
|
||||
|
||||
use crate::app::Cli;
|
||||
use crate::config_file::{self, Config};
|
||||
use crate::flags::Configurable;
|
||||
|
||||
#[test]
|
||||
fn test_from_cli_none() {
|
||||
let argv = ["lsd"];
|
||||
let cli = Cli::try_parse_from(argv).unwrap();
|
||||
assert_eq!(None, TruncateOwner::from_cli(&cli));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_cli_after_some() {
|
||||
let argv = ["lsd", "--truncate-owner-after", "1"];
|
||||
let cli = Cli::try_parse_from(argv).unwrap();
|
||||
assert_eq!(
|
||||
Some(TruncateOwner {
|
||||
after: Some(1),
|
||||
marker: None,
|
||||
}),
|
||||
TruncateOwner::from_cli(&cli)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_cli_marker_some() {
|
||||
let argv = ["lsd", "--truncate-owner-marker", "…"];
|
||||
let cli = Cli::try_parse_from(argv).unwrap();
|
||||
assert_eq!(
|
||||
Some(TruncateOwner {
|
||||
after: None,
|
||||
marker: Some("…".to_string()),
|
||||
}),
|
||||
TruncateOwner::from_cli(&cli)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_config_none() {
|
||||
assert_eq!(None, TruncateOwner::from_config(&Config::with_none()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_config_all_fields_none() {
|
||||
let mut c = Config::with_none();
|
||||
c.truncate_owner = Some(config_file::TruncateOwner {
|
||||
after: None,
|
||||
marker: None,
|
||||
});
|
||||
assert_eq!(
|
||||
Some(TruncateOwner {
|
||||
after: None,
|
||||
marker: None,
|
||||
}),
|
||||
TruncateOwner::from_config(&c)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_config_all_fields_some() {
|
||||
let mut c = Config::with_none();
|
||||
c.truncate_owner = Some(config_file::TruncateOwner {
|
||||
after: Some(1),
|
||||
marker: Some(">".to_string()),
|
||||
});
|
||||
assert_eq!(
|
||||
Some(TruncateOwner {
|
||||
after: Some(1),
|
||||
marker: Some(">".to_string()),
|
||||
}),
|
||||
TruncateOwner::from_config(&c)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
use crate::color::{ColoredString, Colors, Elem};
|
||||
use crate::Flags;
|
||||
#[cfg(unix)]
|
||||
use std::fs::Metadata;
|
||||
|
||||
|
@ -35,12 +36,72 @@ impl From<&Metadata> for Owner {
|
|||
}
|
||||
}
|
||||
|
||||
impl Owner {
|
||||
pub fn render_user(&self, colors: &Colors) -> ColoredString {
|
||||
colors.colorize(self.user.clone(), &Elem::User)
|
||||
fn truncate(input: &str, after: Option<usize>, marker: Option<String>) -> String {
|
||||
let mut output = input.to_string();
|
||||
|
||||
if let Some(after) = after {
|
||||
if output.len() > after {
|
||||
output.truncate(after);
|
||||
|
||||
if let Some(marker) = marker {
|
||||
output.push_str(&marker);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_group(&self, colors: &Colors) -> ColoredString {
|
||||
colors.colorize(self.group.clone(), &Elem::Group)
|
||||
output
|
||||
}
|
||||
|
||||
impl Owner {
|
||||
pub fn render_user(&self, colors: &Colors, flags: &Flags) -> ColoredString {
|
||||
colors.colorize(
|
||||
truncate(
|
||||
&self.user,
|
||||
flags.truncate_owner.after,
|
||||
flags.truncate_owner.marker.clone(),
|
||||
),
|
||||
&Elem::User,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn render_group(&self, colors: &Colors, flags: &Flags) -> ColoredString {
|
||||
colors.colorize(
|
||||
truncate(
|
||||
&self.group,
|
||||
flags.truncate_owner.after,
|
||||
flags.truncate_owner.marker.clone(),
|
||||
),
|
||||
&Elem::Group,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_truncate {
|
||||
use crate::meta::owner::truncate;
|
||||
|
||||
#[test]
|
||||
fn test_none() {
|
||||
assert_eq!("a", truncate("a", None, None));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unchanged_without_marker() {
|
||||
assert_eq!("a", truncate("a", Some(1), None));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unchanged_with_marker() {
|
||||
assert_eq!("a", truncate("a", Some(1), Some("…".to_string())));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_truncated_without_marker() {
|
||||
assert_eq!("a", truncate("ab", Some(1), None));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_truncated_with_marker() {
|
||||
assert_eq!("a…", truncate("ab", Some(1), Some("…".to_string())));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -659,6 +659,24 @@ fn test_upper_case_ext_icon_match() {
|
|||
.stdout(predicate::str::contains("\u{f410}"));
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn test_truncate_owner() {
|
||||
let dir = tempdir();
|
||||
dir.child("foo").touch().unwrap();
|
||||
|
||||
cmd()
|
||||
.arg("-l")
|
||||
.arg("--ignore-config")
|
||||
.arg("--truncate-owner-after")
|
||||
.arg("1")
|
||||
.arg("--truncate-owner-marker")
|
||||
.arg("…")
|
||||
.arg(dir.path())
|
||||
.assert()
|
||||
.stdout(predicate::str::is_match(" .… .… ").unwrap());
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn test_custom_config_file_parsing() {
|
||||
|
|
Loading…
Reference in a new issue