1use std::path::{Component, Path, PathBuf};
11
12use similar::TextDiff;
13
14#[cfg(feature = "script")]
15use crate::error::Result;
16use crate::pattern::CompiledPattern;
17#[cfg(feature = "script")]
18use crate::script::ScriptRewriter;
19
20#[derive(Debug, Clone)]
24pub struct RewriteOutcome {
25 pub after: String,
26 pub matches: usize,
27}
28
29pub fn rewrite_text(pattern: &CompiledPattern, before: &str) -> RewriteOutcome {
33 let regex = pattern.regex();
34 let template = pattern.replacement();
35 let mut matches = 0usize;
36 let after = regex
37 .replace_all(before, |caps: ®ex::Captures<'_>| {
38 matches += 1;
39 let mut dst = String::new();
40 caps.expand(template, &mut dst);
41 dst
42 })
43 .into_owned();
44 RewriteOutcome { after, matches }
45}
46
47#[cfg(feature = "script")]
51pub fn rewrite_text_scripted(
52 pattern: &CompiledPattern,
53 script: &ScriptRewriter,
54 before: &str,
55) -> Result<RewriteOutcome> {
56 use std::cell::RefCell;
57
58 let regex = pattern.regex();
59 let err_slot: RefCell<Option<crate::error::Error>> = RefCell::new(None);
60 let mut matches = 0usize;
61
62 let after = regex.replace_all(before, |caps: ®ex::Captures<'_>| {
63 if err_slot.borrow().is_some() {
64 return String::new();
65 }
66 matches += 1;
67 let caps_vec: Vec<&str> =
68 caps.iter().map(|m| m.map(|m| m.as_str()).unwrap_or("")).collect();
69 match script.replace(&caps_vec) {
70 Ok(s) => s,
71 Err(e) => {
72 *err_slot.borrow_mut() = Some(e);
73 String::new()
74 }
75 }
76 });
77
78 if let Some(e) = err_slot.into_inner() {
79 return Err(e);
80 }
81 Ok(RewriteOutcome { after: after.into_owned(), matches })
82}
83
84pub fn label_for_path(path: &Path) -> String {
88 match path.components().next() {
94 None | Some(Component::CurDir) => {}
95 _ => return path.to_string_lossy().into_owned(),
96 }
97 let mut buf = PathBuf::new();
98 let mut leading = true;
99 for c in path.components() {
100 if leading && matches!(c, Component::CurDir) {
101 continue;
102 }
103 leading = false;
104 buf.push(c.as_os_str());
105 }
106 if buf.as_os_str().is_empty() { ".".to_owned() } else { buf.to_string_lossy().into_owned() }
107}
108
109pub fn unified_diff(label: &str, before: &str, after: &str) -> String {
112 let diff = TextDiff::from_lines(before, after);
113 let mut out = diff
114 .unified_diff()
115 .context_radius(3)
116 .header(&format!("a/{label}"), &format!("b/{label}"))
117 .to_string();
118 if !out.ends_with('\n') {
119 out.push('\n');
120 }
121 out
122}
123
124#[cfg(test)]
125#[path = "rewrite_tests.rs"]
126mod tests;