@@ -184,6 +184,43 @@ impl PartialConfig {
184
184
185
185
:: toml:: to_string ( & cloned) . map_err ( ToTomlError )
186
186
}
187
+
188
+ pub fn from_toml_path ( file_path : & Path ) -> Result < PartialConfig , Error > {
189
+ let mut file = File :: open ( & file_path) ?;
190
+ let mut toml = String :: new ( ) ;
191
+ file. read_to_string ( & mut toml) ?;
192
+ PartialConfig :: from_toml ( & toml) . map_err ( |err| Error :: new ( ErrorKind :: InvalidData , err) )
193
+ }
194
+
195
+ fn from_toml ( toml : & str ) -> Result < PartialConfig , String > {
196
+ let parsed: :: toml:: Value = toml
197
+ . parse ( )
198
+ . map_err ( |e| format ! ( "Could not parse TOML: {}" , e) ) ?;
199
+ let mut err = String :: new ( ) ;
200
+ let table = parsed
201
+ . as_table ( )
202
+ . ok_or_else ( || String :: from ( "Parsed config was not table" ) ) ?;
203
+ for key in table. keys ( ) {
204
+ if !Config :: is_valid_name ( key) {
205
+ let msg = & format ! ( "Warning: Unknown configuration option `{}`\n " , key) ;
206
+ err. push_str ( msg)
207
+ }
208
+ }
209
+ match parsed. try_into ( ) {
210
+ Ok ( parsed_config) => {
211
+ if !err. is_empty ( ) {
212
+ eprint ! ( "{}" , err) ;
213
+ }
214
+ Ok ( parsed_config)
215
+ }
216
+ Err ( e) => {
217
+ err. push_str ( "Error: Decoding config file failed:\n " ) ;
218
+ err. push_str ( format ! ( "{}\n " , e) . as_str ( ) ) ;
219
+ err. push_str ( "Please check your config file." ) ;
220
+ Err ( err)
221
+ }
222
+ }
223
+ }
187
224
}
188
225
189
226
impl Config {
@@ -211,11 +248,8 @@ impl Config {
211
248
/// Returns a `Config` if the config could be read and parsed from
212
249
/// the file, otherwise errors.
213
250
pub fn from_toml_path ( file_path : & Path ) -> Result < Config , Error > {
214
- let mut file = File :: open ( & file_path) ?;
215
- let mut toml = String :: new ( ) ;
216
- file. read_to_string ( & mut toml) ?;
217
- Config :: from_toml ( & toml, file_path. parent ( ) . unwrap ( ) )
218
- . map_err ( |err| Error :: new ( ErrorKind :: InvalidData , err) )
251
+ let partial_config = PartialConfig :: from_toml_path ( file_path) ?;
252
+ Ok ( Config :: default ( ) . fill_from_parsed_config ( partial_config, file_path. parent ( ) . unwrap ( ) ) )
219
253
}
220
254
221
255
/// Resolves the config for input in `dir`.
@@ -227,85 +261,74 @@ impl Config {
227
261
///
228
262
/// Returns the `Config` to use, and the path of the project file if there was
229
263
/// one.
230
- pub fn from_resolved_toml_path ( dir : & Path ) -> Result < ( Config , Option < PathBuf > ) , Error > {
264
+ pub fn from_resolved_toml_path ( dir : & Path ) -> Result < ( Config , Option < Vec < PathBuf > > ) , Error > {
231
265
/// Try to find a project file in the given directory and its parents.
232
266
/// Returns the path of a the nearest project file if one exists,
233
267
/// or `None` if no project file was found.
234
- fn resolve_project_file ( dir : & Path ) -> Result < Option < PathBuf > , Error > {
268
+ fn resolve_project_files ( dir : & Path ) -> Result < Option < Vec < PathBuf > > , Error > {
235
269
let mut current = if dir. is_relative ( ) {
236
270
env:: current_dir ( ) ?. join ( dir)
237
271
} else {
238
272
dir. to_path_buf ( )
239
273
} ;
240
274
241
275
current = dunce:: canonicalize ( current) ?;
276
+ let mut paths = Vec :: new ( ) ;
242
277
243
278
loop {
244
- match get_toml_path ( & current) {
245
- Ok ( Some ( path) ) => return Ok ( Some ( path) ) ,
246
- Err ( e) => return Err ( e) ,
247
- _ => ( ) ,
248
- }
279
+ let current_toml_path = get_toml_path ( & current) ?;
280
+ paths. push ( current_toml_path) ;
249
281
250
282
// If the current directory has no parent, we're done searching.
251
283
if !current. pop ( ) {
252
284
break ;
253
285
}
254
286
}
255
287
288
+ if !paths. is_empty ( ) {
289
+ return Ok ( paths. into_iter ( ) . filter ( |p| p. is_some ( ) ) . collect ( ) ) ;
290
+ }
291
+
256
292
// If nothing was found, check in the home directory.
257
293
if let Some ( home_dir) = dirs:: home_dir ( ) {
258
294
if let Some ( path) = get_toml_path ( & home_dir) ? {
259
- return Ok ( Some ( path) ) ;
295
+ return Ok ( Some ( vec ! [ path] ) ) ;
260
296
}
261
297
}
262
298
263
299
// If none was found ther either, check in the user's configuration directory.
264
300
if let Some ( mut config_dir) = dirs:: config_dir ( ) {
265
301
config_dir. push ( "rustfmt" ) ;
266
302
if let Some ( path) = get_toml_path ( & config_dir) ? {
267
- return Ok ( Some ( path) ) ;
303
+ return Ok ( Some ( vec ! [ path] ) ) ;
268
304
}
269
305
}
270
306
271
307
Ok ( None )
272
308
}
273
309
274
- match resolve_project_file ( dir) ? {
310
+ let files = resolve_project_files ( dir) ;
311
+
312
+ match files? {
275
313
None => Ok ( ( Config :: default ( ) , None ) ) ,
276
- Some ( path) => Config :: from_toml_path ( & path) . map ( |config| ( config, Some ( path) ) ) ,
314
+ Some ( paths) => {
315
+ let mut config = Config :: default ( ) ;
316
+ let mut used_paths = Vec :: with_capacity ( paths. len ( ) ) ;
317
+ for path in paths. into_iter ( ) . rev ( ) {
318
+ let partial_config = PartialConfig :: from_toml_path ( & path) ?;
319
+ config = config. fill_from_parsed_config ( partial_config, & path) ;
320
+ used_paths. push ( path) ;
321
+ }
322
+
323
+ Ok ( ( config, Some ( used_paths) ) )
324
+ }
277
325
}
278
326
}
279
327
280
328
pub fn from_toml ( toml : & str , dir : & Path ) -> Result < Config , String > {
281
- let parsed: :: toml:: Value = toml
282
- . parse ( )
283
- . map_err ( |e| format ! ( "Could not parse TOML: {}" , e) ) ?;
284
- let mut err = String :: new ( ) ;
285
- let table = parsed
286
- . as_table ( )
287
- . ok_or_else ( || String :: from ( "Parsed config was not table" ) ) ?;
288
- for key in table. keys ( ) {
289
- if !Config :: is_valid_name ( key) {
290
- let msg = & format ! ( "Warning: Unknown configuration option `{}`\n " , key) ;
291
- err. push_str ( msg)
292
- }
293
- }
294
- match parsed. try_into ( ) {
295
- Ok ( parsed_config) => {
296
- if !err. is_empty ( ) {
297
- eprint ! ( "{}" , err) ;
298
- }
299
- let config = Config :: default ( ) . fill_from_parsed_config ( parsed_config, dir) ;
300
- Ok ( config)
301
- }
302
- Err ( e) => {
303
- err. push_str ( "Error: Decoding config file failed:\n " ) ;
304
- err. push_str ( format ! ( "{}\n " , e) . as_str ( ) ) ;
305
- err. push_str ( "Please check your config file." ) ;
306
- Err ( err)
307
- }
308
- }
329
+ let partial_config = PartialConfig :: from_toml ( toml) ?;
330
+ let config = Config :: default ( ) . fill_from_parsed_config ( partial_config, dir) ;
331
+ Ok ( config)
309
332
}
310
333
}
311
334
@@ -314,14 +337,14 @@ impl Config {
314
337
pub fn load_config < O : CliOptions > (
315
338
file_path : Option < & Path > ,
316
339
options : Option < & O > ,
317
- ) -> Result < ( Config , Option < PathBuf > ) , Error > {
340
+ ) -> Result < ( Config , Option < Vec < PathBuf > > ) , Error > {
318
341
let over_ride = match options {
319
342
Some ( opts) => config_path ( opts) ?,
320
343
None => None ,
321
344
} ;
322
345
323
346
let result = if let Some ( over_ride) = over_ride {
324
- Config :: from_toml_path ( over_ride. as_ref ( ) ) . map ( |p| ( p, Some ( over_ride. to_owned ( ) ) ) )
347
+ Config :: from_toml_path ( over_ride. as_ref ( ) ) . map ( |p| ( p, Some ( vec ! [ over_ride. to_owned( ) ] ) ) )
325
348
} else if let Some ( file_path) = file_path {
326
349
Config :: from_resolved_toml_path ( file_path)
327
350
} else {
@@ -433,6 +456,42 @@ mod test {
433
456
}
434
457
}
435
458
459
+ struct TempFile {
460
+ path : PathBuf ,
461
+ }
462
+
463
+ fn make_temp_file ( file_name : & ' static str , content : & ' static str ) -> TempFile {
464
+ use std:: env:: var;
465
+
466
+ // Used in the Rust build system.
467
+ let target_dir = var ( "RUSTFMT_TEST_DIR" ) . map_or_else ( |_| env:: temp_dir ( ) , PathBuf :: from) ;
468
+ let path = target_dir. join ( file_name) ;
469
+
470
+ fs:: create_dir_all ( path. parent ( ) . unwrap ( ) ) . expect ( "couldn't create temp file" ) ;
471
+ let mut file = File :: create ( & path) . expect ( "couldn't create temp file" ) ;
472
+ file. write_all ( content. as_bytes ( ) )
473
+ . expect ( "couldn't write temp file" ) ;
474
+ TempFile { path }
475
+ }
476
+
477
+ impl Drop for TempFile {
478
+ fn drop ( & mut self ) {
479
+ use std:: fs:: remove_file;
480
+ remove_file ( & self . path ) . expect ( "couldn't delete temp file" ) ;
481
+ }
482
+ }
483
+
484
+ struct NullOptions ;
485
+
486
+ impl CliOptions for NullOptions {
487
+ fn apply_to ( & self , _: & mut Config ) {
488
+ unreachable ! ( ) ;
489
+ }
490
+ fn config_path ( & self ) -> Option < & Path > {
491
+ unreachable ! ( ) ;
492
+ }
493
+ }
494
+
436
495
#[ test]
437
496
fn test_config_set ( ) {
438
497
let mut config = Config :: default ( ) ;
@@ -585,6 +644,37 @@ ignore = []
585
644
assert_eq ! ( & toml, & default_config) ;
586
645
}
587
646
647
+ #[ test]
648
+ fn test_merged_config ( ) {
649
+ let _outer_config = make_temp_file (
650
+ "a/rustfmt.toml" ,
651
+ r#"
652
+ tab_spaces = 2
653
+ fn_call_width = 50
654
+ ignore = ["b/main.rs", "util.rs"]
655
+ "# ,
656
+ ) ;
657
+
658
+ let inner_config = make_temp_file (
659
+ "a/b/rustfmt.toml" ,
660
+ r#"
661
+ tab_spaces = 3
662
+ ignore = []
663
+ "# ,
664
+ ) ;
665
+
666
+ let inner_dir = inner_config. path . parent ( ) . unwrap ( ) ;
667
+ let ( config, paths) = load_config :: < NullOptions > ( Some ( inner_dir) , None ) . unwrap ( ) ;
668
+
669
+ assert_eq ! ( config. tab_spaces( ) , 3 ) ;
670
+ assert_eq ! ( config. fn_call_width( ) , 50 ) ;
671
+ assert_eq ! ( config. ignore( ) . to_string( ) , r#"["main.rs"]"# ) ;
672
+
673
+ let paths = paths. unwrap ( ) ;
674
+ assert ! ( paths[ 0 ] . ends_with( "a/rustfmt.toml" ) ) ;
675
+ assert ! ( paths[ 1 ] . ends_with( "a/b/rustfmt.toml" ) ) ;
676
+ }
677
+
588
678
mod unstable_features {
589
679
use super :: super :: * ;
590
680
0 commit comments