@@ -101,6 +101,88 @@ fn lint_files() -> impl Iterator<Item=walkdir::DirEntry> {
101
101
. filter ( |f| f. path ( ) . extension ( ) == Some ( OsStr :: new ( "rs" ) ) )
102
102
}
103
103
104
+ /// Replace a region in a file delimited by two lines matching regexes.
105
+ ///
106
+ /// `path` is the relative path to the file on which you want to perform the replacement.
107
+ ///
108
+ /// See `replace_region_in_text` documentation of the other options.
109
+ pub fn replace_region_in_file < F > ( path : & str , start : & str , end : & str , replace_start : bool , replacements : F ) where F : Fn ( ) -> Vec < String > {
110
+ let mut f = fs:: File :: open ( path) . expect ( & format ! ( "File not found: {}" , path) ) ;
111
+ let mut contents = String :: new ( ) ;
112
+ f. read_to_string ( & mut contents) . expect ( "Something went wrong reading the file" ) ;
113
+ let replaced = replace_region_in_text ( & contents, start, end, replace_start, replacements) ;
114
+
115
+ let mut f = fs:: File :: create ( path) . expect ( & format ! ( "File not found: {}" , path) ) ;
116
+ f. write_all ( replaced. as_bytes ( ) ) . expect ( "Unable to write file" ) ;
117
+ // Ensure we write the changes with a trailing newline so that
118
+ // the file has the proper line endings.
119
+ f. write ( b"\n " ) . expect ( "Unable to write file" ) ;
120
+ }
121
+
122
+ /// Replace a region in a text delimited by two lines matching regexes.
123
+ ///
124
+ /// * `text` is the input text on which you want to perform the replacement
125
+ /// * `start` is a `&str` that describes the delimiter line before the region you want to replace.
126
+ /// As the `&str` will be converted to a `Regex`, this can contain regex syntax, too.
127
+ /// * `end` is a `&str` that describes the delimiter line until where the replacement should
128
+ /// happen. As the `&str` will be converted to a `Regex`, this can contain regex syntax, too.
129
+ /// * If `replace_start` is true, the `start` delimiter line is replaced as well.
130
+ /// The `end` delimiter line is never replaced.
131
+ /// * `replacements` is a closure that has to return a `Vec<String>` which contains the new text.
132
+ ///
133
+ /// If you want to perform the replacement on files instead of already parsed text,
134
+ /// use `replace_region_in_file`.
135
+ ///
136
+ /// # Example
137
+ ///
138
+ /// ```
139
+ /// let the_text = "replace_start\nsome text\nthat will be replaced\nreplace_end";
140
+ /// let result = clippy_dev::replace_region_in_text(
141
+ /// the_text,
142
+ /// r#"replace_start"#,
143
+ /// r#"replace_end"#,
144
+ /// false,
145
+ /// || {
146
+ /// vec!["a different".to_string(), "text".to_string()]
147
+ /// }
148
+ /// );
149
+ /// assert_eq!("replace_start\na different\ntext\nreplace_end", result);
150
+ /// ```
151
+ pub fn replace_region_in_text < F > ( text : & str , start : & str , end : & str , replace_start : bool , replacements : F ) -> String where F : Fn ( ) -> Vec < String > {
152
+ let lines = text. lines ( ) ;
153
+ let mut in_old_region = false ;
154
+ let mut found = false ;
155
+ let mut new_lines = vec ! [ ] ;
156
+ let start = Regex :: new ( start) . unwrap ( ) ;
157
+ let end = Regex :: new ( end) . unwrap ( ) ;
158
+
159
+ for line in lines {
160
+ if in_old_region {
161
+ if end. is_match ( & line) {
162
+ in_old_region = false ;
163
+ new_lines. extend ( replacements ( ) ) ;
164
+ new_lines. push ( line. to_string ( ) ) ;
165
+ }
166
+ } else if start. is_match ( & line) {
167
+ if !replace_start {
168
+ new_lines. push ( line. to_string ( ) ) ;
169
+ }
170
+ in_old_region = true ;
171
+ found = true ;
172
+ } else {
173
+ new_lines. push ( line. to_string ( ) ) ;
174
+ }
175
+ }
176
+
177
+ if !found {
178
+ // This happens if the provided regex in `clippy_dev/src/main.rs` is not found in the
179
+ // given text or file. Most likely this is an error on the programmer's side and the Regex
180
+ // is incorrect.
181
+ println ! ( "regex {:?} not found. You may have to update it." , start) ;
182
+ }
183
+ new_lines. join ( "\n " )
184
+ }
185
+
104
186
#[ test]
105
187
fn test_parse_contents ( ) {
106
188
let result: Vec < Lint > = parse_contents (
@@ -140,6 +222,43 @@ declare_deprecated_lint! {
140
222
assert_eq ! ( expected, result) ;
141
223
}
142
224
225
+ #[ test]
226
+ fn test_replace_region ( ) {
227
+ let text = r#"
228
+ abc
229
+ 123
230
+ 789
231
+ def
232
+ ghi"# ;
233
+ let expected = r#"
234
+ abc
235
+ hello world
236
+ def
237
+ ghi"# ;
238
+ let result = replace_region_in_text ( text, r#"^\s*abc$"# , r#"^\s*def"# , false , || {
239
+ vec ! [ "hello world" . to_string( ) ]
240
+ } ) ;
241
+ assert_eq ! ( expected, result) ;
242
+ }
243
+
244
+ #[ test]
245
+ fn test_replace_region_with_start ( ) {
246
+ let text = r#"
247
+ abc
248
+ 123
249
+ 789
250
+ def
251
+ ghi"# ;
252
+ let expected = r#"
253
+ hello world
254
+ def
255
+ ghi"# ;
256
+ let result = replace_region_in_text ( text, r#"^\s*abc$"# , r#"^\s*def"# , true , || {
257
+ vec ! [ "hello world" . to_string( ) ]
258
+ } ) ;
259
+ assert_eq ! ( expected, result) ;
260
+ }
261
+
143
262
#[ test]
144
263
fn test_usable_lints ( ) {
145
264
let lints = vec ! [
0 commit comments