Skip to content

Commit

Permalink
New option file-transformation to transform file paths
Browse files Browse the repository at this point in the history
  • Loading branch information
dandavison committed Jan 6, 2022
1 parent 808ca48 commit 95eb0a0
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 0 deletions.
4 changes: 4 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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}/'
Expand Down
7 changes: 7 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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>,
Expand Down Expand Up @@ -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"],
Expand Down
3 changes: 3 additions & 0 deletions src/options/set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down
6 changes: 6 additions & 0 deletions src/paint.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::borrow::Cow;
use std::collections::HashMap;
use std::io::Write;

Expand Down Expand Up @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions src/utils/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#[cfg(not(tarpaulin_include))]
pub mod bat;
pub mod process;
pub mod regex_replacement;
pub mod syntect;
112 changes: 112 additions & 0 deletions src/utils/regex_replacement.rs
Original file line number Diff line number Diff line change
@@ -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());
}
}

0 comments on commit 95eb0a0

Please sign in to comment.