@@ -170,6 +170,43 @@ impl PartialConfig {
170
170
171
171
:: toml:: to_string ( & cloned) . map_err ( ToTomlError )
172
172
}
173
+
174
+ pub fn from_toml_path ( file_path : & Path ) -> Result < PartialConfig , Error > {
175
+ let mut file = File :: open ( & file_path) ?;
176
+ let mut toml = String :: new ( ) ;
177
+ file. read_to_string ( & mut toml) ?;
178
+ PartialConfig :: from_toml ( & toml) . map_err ( |err| Error :: new ( ErrorKind :: InvalidData , err) )
179
+ }
180
+
181
+ fn from_toml ( toml : & str ) -> Result < PartialConfig , String > {
182
+ let parsed: :: toml:: Value = toml
183
+ . parse ( )
184
+ . map_err ( |e| format ! ( "Could not parse TOML: {}" , e) ) ?;
185
+ let mut err = String :: new ( ) ;
186
+ let table = parsed
187
+ . as_table ( )
188
+ . ok_or_else ( || String :: from ( "Parsed config was not table" ) ) ?;
189
+ for key in table. keys ( ) {
190
+ if !Config :: is_valid_name ( key) {
191
+ let msg = & format ! ( "Warning: Unknown configuration option `{}`\n " , key) ;
192
+ err. push_str ( msg)
193
+ }
194
+ }
195
+ match parsed. try_into ( ) {
196
+ Ok ( parsed_config) => {
197
+ if !err. is_empty ( ) {
198
+ eprint ! ( "{}" , err) ;
199
+ }
200
+ Ok ( parsed_config)
201
+ }
202
+ Err ( e) => {
203
+ err. push_str ( "Error: Decoding config file failed:\n " ) ;
204
+ err. push_str ( format ! ( "{}\n " , e) . as_str ( ) ) ;
205
+ err. push_str ( "Please check your config file." ) ;
206
+ Err ( err)
207
+ }
208
+ }
209
+ }
173
210
}
174
211
175
212
impl Config {
@@ -197,11 +234,8 @@ impl Config {
197
234
/// Returns a `Config` if the config could be read and parsed from
198
235
/// the file, otherwise errors.
199
236
pub fn from_toml_path ( file_path : & Path ) -> Result < Config , Error > {
200
- let mut file = File :: open ( & file_path) ?;
201
- let mut toml = String :: new ( ) ;
202
- file. read_to_string ( & mut toml) ?;
203
- Config :: from_toml ( & toml, file_path. parent ( ) . unwrap ( ) )
204
- . map_err ( |err| Error :: new ( ErrorKind :: InvalidData , err) )
237
+ let partial_config = PartialConfig :: from_toml_path ( file_path) ?;
238
+ Ok ( Config :: default ( ) . fill_from_parsed_config ( partial_config, file_path. parent ( ) . unwrap ( ) ) )
205
239
}
206
240
207
241
/// Resolves the config for input in `dir`.
@@ -213,85 +247,74 @@ impl Config {
213
247
///
214
248
/// Returns the `Config` to use, and the path of the project file if there was
215
249
/// one.
216
- pub fn from_resolved_toml_path ( dir : & Path ) -> Result < ( Config , Option < PathBuf > ) , Error > {
250
+ pub fn from_resolved_toml_path ( dir : & Path ) -> Result < ( Config , Option < Vec < PathBuf > > ) , Error > {
217
251
/// Try to find a project file in the given directory and its parents.
218
252
/// Returns the path of a the nearest project file if one exists,
219
253
/// or `None` if no project file was found.
220
- fn resolve_project_file ( dir : & Path ) -> Result < Option < PathBuf > , Error > {
254
+ fn resolve_project_files ( dir : & Path ) -> Result < Option < Vec < PathBuf > > , Error > {
221
255
let mut current = if dir. is_relative ( ) {
222
256
env:: current_dir ( ) ?. join ( dir)
223
257
} else {
224
258
dir. to_path_buf ( )
225
259
} ;
226
260
227
261
current = dunce:: canonicalize ( current) ?;
262
+ let mut paths = Vec :: new ( ) ;
228
263
229
264
loop {
230
- match get_toml_path ( & current) {
231
- Ok ( Some ( path) ) => return Ok ( Some ( path) ) ,
232
- Err ( e) => return Err ( e) ,
233
- _ => ( ) ,
234
- }
265
+ let current_toml_path = get_toml_path ( & current) ?;
266
+ paths. push ( current_toml_path) ;
235
267
236
268
// If the current directory has no parent, we're done searching.
237
269
if !current. pop ( ) {
238
270
break ;
239
271
}
240
272
}
241
273
274
+ if !paths. is_empty ( ) {
275
+ return Ok ( paths. into_iter ( ) . filter ( |p| p. is_some ( ) ) . collect ( ) ) ;
276
+ }
277
+
242
278
// If nothing was found, check in the home directory.
243
279
if let Some ( home_dir) = dirs:: home_dir ( ) {
244
280
if let Some ( path) = get_toml_path ( & home_dir) ? {
245
- return Ok ( Some ( path) ) ;
281
+ return Ok ( Some ( vec ! [ path] ) ) ;
246
282
}
247
283
}
248
284
249
285
// If none was found ther either, check in the user's configuration directory.
250
286
if let Some ( mut config_dir) = dirs:: config_dir ( ) {
251
287
config_dir. push ( "rustfmt" ) ;
252
288
if let Some ( path) = get_toml_path ( & config_dir) ? {
253
- return Ok ( Some ( path) ) ;
289
+ return Ok ( Some ( vec ! [ path] ) ) ;
254
290
}
255
291
}
256
292
257
293
Ok ( None )
258
294
}
259
295
260
- match resolve_project_file ( dir) ? {
296
+ let files = resolve_project_files ( dir) ;
297
+
298
+ match files? {
261
299
None => Ok ( ( Config :: default ( ) , None ) ) ,
262
- Some ( path) => Config :: from_toml_path ( & path) . map ( |config| ( config, Some ( path) ) ) ,
300
+ Some ( paths) => {
301
+ let mut config = Config :: default ( ) ;
302
+ let mut used_paths = Vec :: with_capacity ( paths. len ( ) ) ;
303
+ for path in paths. into_iter ( ) . rev ( ) {
304
+ let partial_config = PartialConfig :: from_toml_path ( & path) ?;
305
+ config = config. fill_from_parsed_config ( partial_config, & path) ;
306
+ used_paths. push ( path) ;
307
+ }
308
+
309
+ Ok ( ( config, Some ( used_paths) ) )
310
+ }
263
311
}
264
312
}
265
313
266
314
pub fn from_toml ( toml : & str , dir : & Path ) -> Result < Config , String > {
267
- let parsed: :: toml:: Value = toml
268
- . parse ( )
269
- . map_err ( |e| format ! ( "Could not parse TOML: {}" , e) ) ?;
270
- let mut err = String :: new ( ) ;
271
- let table = parsed
272
- . as_table ( )
273
- . ok_or_else ( || String :: from ( "Parsed config was not table" ) ) ?;
274
- for key in table. keys ( ) {
275
- if !Config :: is_valid_name ( key) {
276
- let msg = & format ! ( "Warning: Unknown configuration option `{}`\n " , key) ;
277
- err. push_str ( msg)
278
- }
279
- }
280
- match parsed. try_into ( ) {
281
- Ok ( parsed_config) => {
282
- if !err. is_empty ( ) {
283
- eprint ! ( "{}" , err) ;
284
- }
285
- let config = Config :: default ( ) . fill_from_parsed_config ( parsed_config, dir) ;
286
- Ok ( config)
287
- }
288
- Err ( e) => {
289
- err. push_str ( "Error: Decoding config file failed:\n " ) ;
290
- err. push_str ( format ! ( "{}\n " , e) . as_str ( ) ) ;
291
- err. push_str ( "Please check your config file." ) ;
292
- Err ( err)
293
- }
294
- }
315
+ let partial_config = PartialConfig :: from_toml ( toml) ?;
316
+ let config = Config :: default ( ) . fill_from_parsed_config ( partial_config, dir) ;
317
+ Ok ( config)
295
318
}
296
319
}
297
320
@@ -300,14 +323,14 @@ impl Config {
300
323
pub fn load_config < O : CliOptions > (
301
324
file_path : Option < & Path > ,
302
325
options : Option < & O > ,
303
- ) -> Result < ( Config , Option < PathBuf > ) , Error > {
326
+ ) -> Result < ( Config , Option < Vec < PathBuf > > ) , Error > {
304
327
let over_ride = match options {
305
328
Some ( opts) => config_path ( opts) ?,
306
329
None => None ,
307
330
} ;
308
331
309
332
let result = if let Some ( over_ride) = over_ride {
310
- Config :: from_toml_path ( over_ride. as_ref ( ) ) . map ( |p| ( p, Some ( over_ride. to_owned ( ) ) ) )
333
+ Config :: from_toml_path ( over_ride. as_ref ( ) ) . map ( |p| ( p, Some ( vec ! [ over_ride. to_owned( ) ] ) ) )
311
334
} else if let Some ( file_path) = file_path {
312
335
Config :: from_resolved_toml_path ( file_path)
313
336
} else {
@@ -417,6 +440,42 @@ mod test {
417
440
}
418
441
}
419
442
443
+ struct TempFile {
444
+ path : PathBuf ,
445
+ }
446
+
447
+ fn make_temp_file ( file_name : & ' static str , content : & ' static str ) -> TempFile {
448
+ use std:: env:: var;
449
+
450
+ // Used in the Rust build system.
451
+ let target_dir = var ( "RUSTFMT_TEST_DIR" ) . map_or_else ( |_| env:: temp_dir ( ) , PathBuf :: from) ;
452
+ let path = target_dir. join ( file_name) ;
453
+
454
+ fs:: create_dir_all ( path. parent ( ) . unwrap ( ) ) . expect ( "couldn't create temp file" ) ;
455
+ let mut file = File :: create ( & path) . expect ( "couldn't create temp file" ) ;
456
+ file. write_all ( content. as_bytes ( ) )
457
+ . expect ( "couldn't write temp file" ) ;
458
+ TempFile { path }
459
+ }
460
+
461
+ impl Drop for TempFile {
462
+ fn drop ( & mut self ) {
463
+ use std:: fs:: remove_file;
464
+ remove_file ( & self . path ) . expect ( "couldn't delete temp file" ) ;
465
+ }
466
+ }
467
+
468
+ struct NullOptions ;
469
+
470
+ impl CliOptions for NullOptions {
471
+ fn apply_to ( & self , _: & mut Config ) {
472
+ unreachable ! ( ) ;
473
+ }
474
+ fn config_path ( & self ) -> Option < & Path > {
475
+ unreachable ! ( ) ;
476
+ }
477
+ }
478
+
420
479
#[ test]
421
480
fn test_config_set ( ) {
422
481
let mut config = Config :: default ( ) ;
@@ -568,6 +627,37 @@ ignore = []
568
627
assert_eq ! ( & toml, & default_config) ;
569
628
}
570
629
630
+ #[ test]
631
+ fn test_merged_config ( ) {
632
+ let _outer_config = make_temp_file (
633
+ "a/rustfmt.toml" ,
634
+ r#"
635
+ tab_spaces = 2
636
+ fn_call_width = 50
637
+ ignore = ["b/main.rs", "util.rs"]
638
+ "# ,
639
+ ) ;
640
+
641
+ let inner_config = make_temp_file (
642
+ "a/b/rustfmt.toml" ,
643
+ r#"
644
+ tab_spaces = 3
645
+ ignore = []
646
+ "# ,
647
+ ) ;
648
+
649
+ let inner_dir = inner_config. path . parent ( ) . unwrap ( ) ;
650
+ let ( config, paths) = load_config :: < NullOptions > ( Some ( inner_dir) , None ) . unwrap ( ) ;
651
+
652
+ assert_eq ! ( config. tab_spaces( ) , 3 ) ;
653
+ assert_eq ! ( config. fn_call_width( ) , 50 ) ;
654
+ assert_eq ! ( config. ignore( ) . to_string( ) , r#"["main.rs"]"# ) ;
655
+
656
+ let paths = paths. unwrap ( ) ;
657
+ assert ! ( paths[ 0 ] . ends_with( "a/rustfmt.toml" ) ) ;
658
+ assert ! ( paths[ 1 ] . ends_with( "a/b/rustfmt.toml" ) ) ;
659
+ }
660
+
571
661
mod unstable_features {
572
662
use super :: super :: * ;
573
663
0 commit comments