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