summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDan Davison <dandavison7@gmail.com>2021-12-19 16:17:27 -0500
committerDan Davison <dandavison7@gmail.com>2022-01-06 12:20:17 -0500
commit95eb0a0e589d6ee899a4a383aa2fb930462fc5cc (patch)
tree975bbb3269d9af14e54e620ca0c0009c4fbdc0e1
parent808ca48eefa41facf9a30842cb500654208e2156 (diff)
New option file-transformation to transform file paths
-rw-r--r--src/cli.rs4
-rw-r--r--src/config.rs7
-rw-r--r--src/options/set.rs3
-rw-r--r--src/paint.rs6
-rw-r--r--src/utils/mod.rs1
-rw-r--r--src/utils/regex_replacement.rs112
6 files changed, 133 insertions, 0 deletions
diff --git a/src/cli.rs b/src/cli.rs
index 569be55f..ed169809 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -392,6 +392,10 @@ pub struct Opt {
/// (overline), or the combination 'ul ol'.
pub file_decoration_style: String,
+ #[structopt(long = "file-transformation")]
+ /// A sed-style command specifying how file paths should be transformed for display.
+ pub file_regex_replacement: Option<String>,
+
/// Format string for commit hyperlinks (requires --hyperlinks). The
/// placeholder "{commit}" will be replaced by the commit hash. For example:
/// --hyperlinks-commit-link-format='https://mygitrepo/{commit}/'
diff --git a/src/config.rs b/src/config.rs
index 1eb87786..1b8e759b 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -25,6 +25,7 @@ use crate::style;
use crate::style::Style;
use crate::tests::TESTING;
use crate::utils::bat::output::PagingMode;
+use crate::utils::regex_replacement::RegexReplacement;
use crate::utils::syntect::FromDeltaStyle;
use crate::wrapping::WrapConfig;
@@ -80,6 +81,7 @@ pub struct Config {
pub file_modified_label: String,
pub file_removed_label: String,
pub file_renamed_label: String,
+ pub file_regex_replacement: Option<RegexReplacement>,
pub right_arrow: String,
pub file_style: Style,
pub git_config_entries: HashMap<String, GitConfigEntry>,
@@ -263,6 +265,11 @@ impl From<cli::Opt> for Config {
file_modified_label,
file_removed_label,
file_renamed_label,
+ file_regex_replacement: opt
+ .file_regex_replacement
+ .as_deref()
+ .map(RegexReplacement::from_sed_command)
+ .flatten(),
right_arrow,
hunk_label,
file_style: styles["file-style"],
diff --git a/src/options/set.rs b/src/options/set.rs
index be3d74a1..09327a0b 100644
--- a/src/options/set.rs
+++ b/src/options/set.rs
@@ -143,6 +143,7 @@ pub fn set_options(
file_modified_label,
file_removed_label,
file_renamed_label,
+ file_regex_replacement,
right_arrow,
hunk_label,
file_style,
@@ -704,6 +705,7 @@ pub mod tests {
file-modified-label = xxxyyyzzz
file-removed-label = xxxyyyzzz
file-renamed-label = xxxyyyzzz
+ file-transformation = s/foo/bar/
right-arrow = xxxyyyzzz
file-style = black black
hunk-header-decoration-style = black black
@@ -771,6 +773,7 @@ pub mod tests {
assert_eq!(opt.file_renamed_label, "xxxyyyzzz");
assert_eq!(opt.right_arrow, "xxxyyyzzz");
assert_eq!(opt.file_style, "black black");
+ assert_eq!(opt.file_regex_replacement, Some("s/foo/bar/".to_string()));
assert_eq!(opt.hunk_header_decoration_style, "black black");
assert_eq!(opt.hunk_header_style, "black black");
assert_eq!(opt.keep_plus_minus_markers, true);
diff --git a/src/paint.rs b/src/paint.rs
index 6d00cfc5..ed9185a0 100644
--- a/src/paint.rs
+++ b/src/paint.rs
@@ -1,3 +1,4 @@
+use std::borrow::Cow;
use std::collections::HashMap;
use std::io::Write;
@@ -784,6 +785,11 @@ pub fn paint_file_path_with_line_number(
) -> String {
let mut file_with_line_number = Vec::new();
if let Some(file_style) = file_style {
+ let plus_file = if let Some(regex_replacement) = &config.file_regex_replacement {
+ regex_replacement.execute(plus_file)
+ } else {
+ Cow::from(plus_file)
+ };
file_with_line_number.push(file_style.paint(plus_file))
};
if let Some(line_number) = line_number {
diff --git a/src/utils/mod.rs b/src/utils/mod.rs
index 70026dd3..46ea9906 100644
--- a/src/utils/mod.rs
+++ b/src/utils/mod.rs
@@ -1,4 +1,5 @@
#[cfg(not(tarpaulin_include))]
pub mod bat;
pub mod process;
+pub mod regex_replacement;
pub mod syntect;
diff --git a/src/utils/regex_replacement.rs b/src/utils/regex_replacement.rs
new file mode 100644
index 00000000..5f07902e
--- /dev/null
+++ b/src/utils/regex_replacement.rs
@@ -0,0 +1,112 @@
+use std::borrow::Cow;
+
+use regex::{Regex, RegexBuilder};
+
+#[derive(Clone, Debug)]
+pub struct RegexReplacement {
+ regex: Regex,
+ replacement: String,
+ replace_all: bool,
+}
+
+impl RegexReplacement {
+ pub fn from_sed_command(sed_command: &str) -> Option<Self> {
+ let sep = sed_command.chars().nth(1)?;
+ let mut parts = sed_command[2..].split(sep);
+ let regex = parts.next()?;
+ let replacement = parts.next()?.to_string();
+ let flags = parts.next()?;
+ let mut re_builder = RegexBuilder::new(regex);
+ let mut replace_all = false;
+ for flag in flags.chars() {
+ match flag {
+ 'g' => {
+ replace_all = true;
+ }
+ 'i' => {
+ re_builder.case_insensitive(true);
+ }
+ 'm' => {
+ re_builder.multi_line(true);
+ }
+ 's' => {
+ re_builder.dot_matches_new_line(true);
+ }
+ 'U' => {
+ re_builder.swap_greed(true);
+ }
+ 'x' => {
+ re_builder.ignore_whitespace(true);
+ }
+ _ => {}
+ }
+ }
+ let regex = re_builder.build().ok()?;
+ Some(RegexReplacement {
+ regex,
+ replacement,
+ replace_all,
+ })
+ }
+
+ pub fn execute<'t>(&self, s: &'t str) -> Cow<'t, str> {
+ if self.replace_all {
+ self.regex.replace_all(s, &self.replacement)
+ } else {
+ self.regex.replace(s, &self.replacement)
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_sed_command() {
+ let command = "s,foo,bar,";
+ let rr = RegexReplacement::from_sed_command(command).unwrap();
+ assert_eq!(rr.regex.as_str(), "foo");
+ assert_eq!(rr.replacement, "bar");
+ assert_eq!(rr.replace_all, false);
+ assert_eq!(rr.execute("foo"), "bar");
+ }
+
+ #[test]
+ fn test_sed_command_i_flag() {
+ let command = "s,FOO,bar,";
+ let rr = RegexReplacement::from_sed_command(command).unwrap();
+ assert_eq!(rr.execute("foo"), "foo");
+ let command = "s,FOO,bar,i";
+ let rr = RegexReplacement::from_sed_command(command).unwrap();
+ assert_eq!(rr.execute("foo"), "bar");
+ }
+
+ #[test]
+ fn test_sed_command_g_flag() {
+ let command = "s,foo,bar,";
+ let rr = RegexReplacement::from_sed_command(command).unwrap();
+ assert_eq!(rr.execute("foofoo"), "barfoo");
+ let command = "s,foo,bar,g";
+ let rr = RegexReplacement::from_sed_command(command).unwrap();
+ assert_eq!(rr.execute("foofoo"), "barbar");
+ }
+
+ #[test]
+ fn test_sed_command_with_named_captures() {
+ let command = r"s/(?P<last>[^,\s]+),\s+(?P<first>\S+)/$first $last/";
+ let rr = RegexReplacement::from_sed_command(command).unwrap();
+ assert_eq!(rr.execute("Springsteen, Bruce"), "Bruce Springsteen");
+ }
+
+ #[test]
+ fn test_sed_command_invalid() {
+ assert!(RegexReplacement::from_sed_command("").is_none());
+ assert!(RegexReplacement::from_sed_command("s").is_none());
+ assert!(RegexReplacement::from_sed_command("s,").is_none());
+ assert!(RegexReplacement::from_sed_command("s,,").is_none());
+ assert!(RegexReplacement::from_sed_command("s,,i").is_none());
+ assert!(RegexReplacement::from_sed_command("s,,,").is_some());
+ assert!(RegexReplacement::from_sed_command("s,,,i").is_some());
+ }
+}