1+ mod line_replacer;
2+
13use crate :: { utils, Error , Result } ;
24use 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+
1221impl 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