@@ -49,6 +49,10 @@ pub struct Opts {
49
49
) ]
50
50
packages : Vec < String > ,
51
51
52
+ /// Specify a source file to format
53
+ #[ clap( short = 's' , long = "src-file" , value_name = "src-file" ) ]
54
+ src_file : Option < PathBuf > ,
55
+
52
56
/// Specify path to Cargo.toml
53
57
#[ clap( long = "manifest-path" , value_name = "manifest-path" ) ]
54
58
manifest_path : Option < String > ,
@@ -94,6 +98,28 @@ fn execute() -> i32 {
94
98
95
99
let opts = Opts :: parse_from ( args) ;
96
100
101
+ if opts. src_file . is_some ( ) & !opts. packages . is_empty ( ) {
102
+ print_usage_to_stderr ( "cannot format source files and packages at the same time" ) ;
103
+ return FAILURE ;
104
+ }
105
+
106
+ if opts. src_file . is_some ( ) & opts. format_all {
107
+ print_usage_to_stderr ( "cannot format all packages when specifying source files" ) ;
108
+ return FAILURE ;
109
+ }
110
+
111
+ if opts. rustfmt_options . iter ( ) . any ( |s| s. ends_with ( ".rs" ) ) {
112
+ print_usage_to_stderr (
113
+ "cannot pass rust files to rustfmt through cargo-fmt. Use '--src-file' instead" ,
114
+ ) ;
115
+ return FAILURE ;
116
+ }
117
+
118
+ if opts. rustfmt_options . iter ( ) . any ( |s| s. contains ( "--edition" ) ) {
119
+ print_usage_to_stderr ( "cannot pass '--edition' to rustfmt through cargo-fmt" ) ;
120
+ return FAILURE ;
121
+ }
122
+
97
123
let verbosity = match ( opts. verbose , opts. quiet ) {
98
124
( false , false ) => Verbosity :: Normal ,
99
125
( false , true ) => Verbosity :: Quiet ,
@@ -319,17 +345,23 @@ pub enum CargoFmtStrategy {
319
345
/// Format every packages and dependencies.
320
346
All ,
321
347
/// Format packages that are specified by the command line argument.
322
- Some ( Vec < String > ) ,
348
+ Packages ( Vec < String > ) ,
323
349
/// Format the root packages only.
324
350
Root ,
351
+ /// Format individual source files specified by the command line arguments.
352
+ SourceFile ( PathBuf ) ,
325
353
}
326
354
327
355
impl CargoFmtStrategy {
328
356
pub fn from_opts ( opts : & Opts ) -> CargoFmtStrategy {
357
+ if let Some ( ref src_file) = opts. src_file {
358
+ return CargoFmtStrategy :: SourceFile ( src_file. clone ( ) ) ;
359
+ }
360
+
329
361
match ( opts. format_all , opts. packages . is_empty ( ) ) {
330
362
( false , true ) => CargoFmtStrategy :: Root ,
331
363
( true , _) => CargoFmtStrategy :: All ,
332
- ( false , false ) => CargoFmtStrategy :: Some ( opts. packages . clone ( ) ) ,
364
+ ( false , false ) => CargoFmtStrategy :: Packages ( opts. packages . clone ( ) ) ,
333
365
}
334
366
}
335
367
}
@@ -346,9 +378,12 @@ fn get_targets(
346
378
CargoFmtStrategy :: All => {
347
379
get_targets_recursive ( manifest_path, & mut targets, & mut BTreeSet :: new ( ) ) ?
348
380
}
349
- CargoFmtStrategy :: Some ( ref hitlist) => {
381
+ CargoFmtStrategy :: Packages ( ref hitlist) => {
350
382
get_targets_with_hitlist ( manifest_path, hitlist, & mut targets) ?
351
383
}
384
+ CargoFmtStrategy :: SourceFile ( ref src_file) => {
385
+ get_target_from_src_file ( manifest_path, & src_file, & mut targets) ?
386
+ }
352
387
}
353
388
354
389
if targets. is_empty ( ) {
@@ -361,6 +396,57 @@ fn get_targets(
361
396
}
362
397
}
363
398
399
+ fn get_target_from_src_file (
400
+ manifest_path : Option < & Path > ,
401
+ src_file : & PathBuf ,
402
+ targets : & mut BTreeSet < Target > ,
403
+ ) -> Result < ( ) , io:: Error > {
404
+ let metadata = get_cargo_metadata ( manifest_path) ?;
405
+
406
+ let get_target = |src_path : & Path | {
407
+ metadata
408
+ . packages
409
+ . iter ( )
410
+ . map ( |p| p. targets . iter ( ) )
411
+ . flatten ( )
412
+ . filter ( |t| {
413
+ let kind = & t. kind [ 0 ] ;
414
+ // to prevent formatting any arbitrary file within the root of our
415
+ // project we special case the build.rs script, becuase it's parent
416
+ // is the root of the project and we would always select the custom-build
417
+ // target in the event that we couldn't find a better target to associate
418
+ // with our file.
419
+ if kind == "custom-build" && !src_path. ends_with ( "build.rs" ) {
420
+ return false ;
421
+ }
422
+
423
+ if let Ok ( target_path) = fs:: canonicalize ( & t. src_path ) {
424
+ let target_dir = target_path
425
+ . parent ( )
426
+ . expect ( "Target src_path should have a parent directory" ) ;
427
+ src_path. starts_with ( target_dir)
428
+ } else {
429
+ false
430
+ }
431
+ } )
432
+ . max_by ( |t1, t2| {
433
+ let t1_len = t1. src_path . components ( ) . count ( ) ;
434
+ let t2_len = t2. src_path . components ( ) . count ( ) ;
435
+ t1_len. cmp ( & t2_len)
436
+ } )
437
+ } ;
438
+
439
+ let canonicalize = fs:: canonicalize ( & src_file) ?;
440
+ if let Some ( target) = get_target ( & canonicalize) {
441
+ targets. insert ( Target {
442
+ path : src_file. to_owned ( ) ,
443
+ kind : target. kind [ 0 ] . clone ( ) ,
444
+ edition : target. edition . clone ( ) ,
445
+ } ) ;
446
+ }
447
+ Ok ( ( ) )
448
+ }
449
+
364
450
fn get_targets_root_only (
365
451
manifest_path : Option < & Path > ,
366
452
targets : & mut BTreeSet < Target > ,
0 commit comments