Skip to content

Commit 8903d49

Browse files
committed
example implementation
1 parent d1fd7c6 commit 8903d49

File tree

2 files changed

+178
-66
lines changed

2 files changed

+178
-66
lines changed

src/replacer/line_replacer.rs

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#[derive(Debug)]
2+
pub struct Pair {
3+
regex: regex::Regex,
4+
rep: String,
5+
}
6+
7+
#[derive(Debug)]
8+
pub enum Opt {
9+
ReplaceLineIfMatch,
10+
AnotherOption,
11+
}
12+
13+
pub fn replace_line<P: AsRef<[Pair]>, S: AsRef<str>>(
14+
pairs: P,
15+
opts: Vec<Opt>,
16+
content: S,
17+
) -> Vec<String> {
18+
let pairs = pairs.as_ref();
19+
let content = content.as_ref();
20+
21+
let mut lines = content.lines().map(|x| x.to_owned()).collect::<Vec<_>>();
22+
23+
for opt in opts {
24+
match opt {
25+
Opt::ReplaceLineIfMatch => replace_line_if_match(pairs, &mut lines),
26+
Opt::AnotherOption => other(pairs, &mut lines),
27+
}
28+
}
29+
lines
30+
}
31+
32+
fn replace_line_if_match(pairs: &[Pair], lines: &mut Vec<String>) {
33+
for line in lines.iter_mut() {
34+
for Pair { regex, rep } in pairs {
35+
if regex.is_match(&line) {
36+
*line = rep.clone();
37+
}
38+
}
39+
}
40+
}
41+
42+
fn other(pairs: &[Pair], lines: &mut Vec<String>) {
43+
unimplemented!()
44+
}

src/replacer.rs src/replacer/mod.rs

+134-66
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,55 @@
1+
mod line_replacer;
2+
13
use crate::{utils, Error, Result};
24
use regex::bytes::Regex;
3-
use std::{fs, fs::File, io::prelude::*, path::Path};
5+
use std::{borrow::Cow, fs, fs::File, io::prelude::*, path::Path};
46

5-
pub(crate) struct Replacer {
7+
#[derive(Debug)]
8+
struct Pair {
69
regex: Regex,
7-
replace_with: Vec<u8>,
8-
is_literal: bool,
9-
replacements: usize,
10+
rep: Vec<u8>,
11+
}
12+
13+
#[derive(Debug)]
14+
pub(crate) struct Replacer {
15+
pairs: Vec<Pair>,
16+
is_literal: bool, // -s
17+
max_replacements: usize,
1018
}
1119

20+
1221
impl Replacer {
1322
pub(crate) fn new(
1423
look_for: String,
1524
replace_with: String,
1625
is_literal: bool,
1726
flags: Option<String>,
1827
replacements: Option<usize>,
28+
extra: Vec<String>,
1929
) -> Result<Self> {
20-
let (look_for, replace_with) = if is_literal {
21-
(regex::escape(&look_for), replace_with.into_bytes())
22-
} else {
23-
(
24-
look_for,
25-
utils::unescape(&replace_with)
26-
.unwrap_or(replace_with)
27-
.into_bytes(),
28-
)
29-
};
30-
31-
let mut regex = regex::bytes::RegexBuilder::new(&look_for);
32-
regex.multi_line(true);
33-
34-
if let Some(flags) = flags {
35-
flags.chars().for_each(|c| {
36-
#[rustfmt::skip]
30+
fn create(
31+
look_for: String,
32+
replace_with: String,
33+
is_literal: bool,
34+
flags: Option<&str>,
35+
) -> Result<Pair> {
36+
let (look_for, replace_with) = if is_literal {
37+
(regex::escape(&look_for), replace_with.into_bytes())
38+
} else {
39+
(
40+
look_for,
41+
utils::unescape(&replace_with)
42+
.unwrap_or(replace_with)
43+
.into_bytes(),
44+
)
45+
};
46+
47+
let mut regex = regex::bytes::RegexBuilder::new(&look_for);
48+
regex.multi_line(true);
49+
50+
if let Some(flags) = flags {
51+
for c in flags.chars() {
52+
#[rustfmt::skip]
3753
match c {
3854
'c' => { regex.case_insensitive(false); },
3955
'i' => { regex.case_insensitive(true); },
@@ -53,19 +69,46 @@ impl Replacer {
5369
},
5470
_ => {},
5571
};
56-
});
57-
};
72+
}
73+
};
74+
Ok(Pair {
75+
regex: regex.build()?,
76+
rep: replace_with,
77+
})
78+
}
5879

59-
Ok(Self {
60-
regex: regex.build()?,
80+
let capacity = extra.len() / 2 + 1;
81+
let mut pairs = Vec::with_capacity(capacity);
82+
pairs.push(create(
83+
look_for,
6184
replace_with,
6285
is_literal,
63-
replacements: replacements.unwrap_or(0),
86+
flags.as_deref(),
87+
)?);
88+
89+
let mut it = extra.into_iter();
90+
while let Some(look_for) = it.next() {
91+
let replace_with = it
92+
.next()
93+
.expect("The extra pattern list doesn't have an even lenght");
94+
95+
pairs.push(create(
96+
look_for,
97+
replace_with,
98+
is_literal,
99+
flags.as_deref(),
100+
)?);
101+
}
102+
103+
Ok(Self {
104+
pairs,
105+
is_literal,
106+
max_replacements: replacements.unwrap_or(0),
64107
})
65108
}
66109

67110
pub(crate) fn has_matches(&self, content: &[u8]) -> bool {
68-
self.regex.is_match(content)
111+
self.pairs.iter().any(|r| r.regex.is_match(content))
69112
}
70113

71114
pub(crate) fn check_not_empty(mut file: File) -> Result<()> {
@@ -74,50 +117,56 @@ impl Replacer {
74117
Ok(())
75118
}
76119

77-
pub(crate) fn replace<'a>(
78-
&'a self,
79-
content: &'a [u8],
80-
) -> std::borrow::Cow<'a, [u8]> {
81-
if self.is_literal {
82-
self.regex.replacen(
83-
content,
84-
self.replacements,
85-
regex::bytes::NoExpand(&self.replace_with),
86-
)
87-
} else {
88-
self.regex
89-
.replacen(content, self.replacements, &*self.replace_with)
120+
pub(crate) fn replace<'a>(&'a self, content: &'a [u8]) -> Cow<'a, [u8]> {
121+
let mut result = Cow::Borrowed(content);
122+
for Pair { regex, rep } in self.pairs.iter() {
123+
let res = if self.is_literal {
124+
let rep = regex::bytes::NoExpand(rep.as_slice());
125+
regex.replacen(&result, self.max_replacements, rep)
126+
} else {
127+
regex.replacen(&result, self.max_replacements, rep)
128+
};
129+
130+
result = Cow::Owned(res.into_owned());
90131
}
132+
result
91133
}
92134

93135
pub(crate) fn replace_preview<'a>(
94136
&'a self,
95-
content: &[u8],
96-
) -> std::borrow::Cow<'a, [u8]> {
97-
let mut v = Vec::<u8>::new();
98-
let mut captures = self.regex.captures_iter(content);
99-
100-
self.regex.split(content).for_each(|sur_text| {
101-
use regex::bytes::Replacer;
102-
103-
v.extend(sur_text);
104-
if let Some(capture) = captures.next() {
105-
v.extend_from_slice(
106-
ansi_term::Color::Green.prefix().to_string().as_bytes(),
107-
);
108-
if self.is_literal {
109-
regex::bytes::NoExpand(&self.replace_with)
110-
.replace_append(&capture, &mut v);
111-
} else {
112-
(&*self.replace_with).replace_append(&capture, &mut v);
137+
content: &'a [u8],
138+
) -> Cow<'a, [u8]> {
139+
let mut content = Cow::Borrowed(content);
140+
141+
for Pair { regex, rep } in self.pairs.iter() {
142+
let rep = rep.as_slice();
143+
144+
let mut v = Vec::<u8>::new();
145+
let mut captures = regex.captures_iter(&content);
146+
147+
for sur_text in regex.split(&content) {
148+
use regex::bytes::Replacer;
149+
150+
v.extend(sur_text);
151+
if let Some(capture) = captures.next() {
152+
v.extend_from_slice(
153+
ansi_term::Color::Green.prefix().to_string().as_bytes(),
154+
);
155+
if self.is_literal {
156+
regex::bytes::NoExpand(&rep)
157+
.replace_append(&capture, &mut v);
158+
} else {
159+
(&*rep).replace_append(&capture, &mut v);
160+
}
161+
v.extend_from_slice(
162+
ansi_term::Color::Green.suffix().to_string().as_bytes(),
163+
);
113164
}
114-
v.extend_from_slice(
115-
ansi_term::Color::Green.suffix().to_string().as_bytes(),
116-
);
117165
}
118-
});
166+
content = Cow::Owned(v);
167+
}
119168

120-
return std::borrow::Cow::Owned(v);
169+
content
121170
}
122171

123172
pub(crate) fn replace_file(&self, path: &Path) -> Result<()> {
@@ -130,7 +179,7 @@ impl Replacer {
130179

131180
let source = File::open(path)?;
132181
let meta = fs::metadata(path)?;
133-
let mmap_source = unsafe { Mmap::map(&source)? };
182+
let mmap_source = unsafe { Mmap::map(&source) }?;
134183
let replaced = self.replace(&mmap_source);
135184

136185
let target = tempfile::NamedTempFile::new_in(
@@ -142,7 +191,7 @@ impl Replacer {
142191
file.set_permissions(meta.permissions())?;
143192

144193
if !replaced.is_empty() {
145-
let mut mmap_target = unsafe { MmapMut::map_mut(file)? };
194+
let mut mmap_target = unsafe { MmapMut::map_mut(file) }?;
146195
mmap_target.deref_mut().write_all(&replaced)?;
147196
mmap_target.flush_async()?;
148197
}
@@ -173,6 +222,7 @@ mod tests {
173222
literal,
174223
flags.map(ToOwned::to_owned),
175224
None,
225+
vec![],
176226
)
177227
.unwrap();
178228
assert_eq!(
@@ -216,4 +266,22 @@ mod tests {
216266
fn full_word_replace() {
217267
replace("abc", "def", false, Some("w"), "abcd abc", "abcd def");
218268
}
269+
270+
#[test]
271+
fn test_multipattern() {
272+
let replacer = Replacer::new(
273+
"foo".to_owned(),
274+
"bar".to_owned(),
275+
false,
276+
None,
277+
None,
278+
vec!["qux".into(), "quux".into(), "bing".into(), "bong".into()],
279+
)
280+
.unwrap();
281+
282+
assert_eq!(
283+
std::str::from_utf8(&replacer.replace("foo qux bing".as_bytes())),
284+
Ok("bar quux bong")
285+
);
286+
}
219287
}

0 commit comments

Comments
 (0)