Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add configuration and CLI options: truncate owner #905

Merged
merged 1 commit into from
Sep 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
6 changes: 6 additions & 0 deletions doc/lsd.md
Original file line number Diff line number Diff line change
Expand Up @@ -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>...`
Expand Down
8 changes: 8 additions & 0 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))]
Expand Down
21 changes: 21 additions & 0 deletions src/config_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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 {
Expand All @@ -97,6 +104,7 @@ impl Config {
symlink_arrow: None,
hyperlink: None,
header: None,
truncate_owner: None,
}
}

Expand Down Expand Up @@ -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)]
Expand Down Expand Up @@ -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
);
Expand Down
4 changes: 2 additions & 2 deletions src/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
4 changes: 4 additions & 0 deletions src/flags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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,
}

Expand Down Expand Up @@ -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,
})
}
Expand Down
120 changes: 120 additions & 0 deletions src/flags/truncate_owner.rs
Original file line number Diff line number Diff line change
@@ -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)
);
}
}
69 changes: 65 additions & 4 deletions src/meta/owner.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::color::{ColoredString, Colors, Elem};
use crate::Flags;
#[cfg(unix)]
use std::fs::Metadata;

Expand Down Expand Up @@ -35,12 +36,72 @@ impl From<&Metadata> for Owner {
}
}

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);
}
}
}

output
}

impl Owner {
pub fn render_user(&self, colors: &Colors) -> ColoredString {
colors.colorize(self.user.clone(), &Elem::User)
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));
}

pub fn render_group(&self, colors: &Colors) -> ColoredString {
colors.colorize(self.group.clone(), &Elem::Group)
#[test]
fn test_truncated_with_marker() {
assert_eq!("a…", truncate("ab", Some(1), Some("…".to_string())));
}
}
18 changes: 18 additions & 0 deletions tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
Loading