12
12
13
13
#[ macro_use]
14
14
extern crate log;
15
+ #[ macro_use]
16
+ extern crate nom;
17
+
15
18
extern crate rustfmt;
16
19
extern crate toml;
17
20
extern crate env_logger;
18
21
extern crate getopts;
19
22
23
+ use nom:: IResult ;
24
+
20
25
use rustfmt:: { run, run_from_stdin} ;
21
26
use rustfmt:: config:: { Config , WriteMode } ;
27
+ use rustfmt:: config:: { FileLinesMap , LineRanges } ;
22
28
23
29
use std:: env;
24
30
use std:: fs:: { self , File } ;
@@ -50,6 +56,10 @@ enum Operation {
50
56
input : String ,
51
57
config_path : Option < PathBuf > ,
52
58
} ,
59
+ /// Format a set of line ranges.
60
+ FormatLineRanges {
61
+ file_lines_map : FileLinesMap ,
62
+ } ,
53
63
}
54
64
55
65
/// Try to find a project file in the given directory and its parents. Returns the path of a the
@@ -120,7 +130,9 @@ fn match_cli_path_or_file(config_path: Option<PathBuf>,
120
130
121
131
fn update_config ( config : & mut Config , matches : & Matches ) -> Result < ( ) , String > {
122
132
config. verbose = matches. opt_present ( "verbose" ) ;
123
- config. skip_children = matches. opt_present ( "skip-children" ) ;
133
+ // `file-lines` implies `skip-children`.
134
+ config. skip_children = matches. opt_present ( "skip-children" ) ||
135
+ ( file_lines_enabled ( ) && matches. opt_present ( "file-lines" ) ) ;
124
136
125
137
let write_mode = matches. opt_str ( "write-mode" ) ;
126
138
match matches. opt_str ( "write-mode" ) . map ( |wm| WriteMode :: from_str ( & wm) ) {
@@ -133,6 +145,13 @@ fn update_config(config: &mut Config, matches: &Matches) -> Result<(), String> {
133
145
}
134
146
}
135
147
148
+ fn file_lines_enabled ( ) -> bool {
149
+ match env:: var ( "RUSTFMT_EXPERIMENTAL_FILE_LINES" ) {
150
+ Ok ( ref v) if v == "1" => true ,
151
+ _ => false ,
152
+ }
153
+ }
154
+
136
155
fn execute ( ) -> i32 {
137
156
let mut opts = Options :: new ( ) ;
138
157
opts. optflag ( "h" , "help" , "show this message" ) ;
@@ -152,6 +171,13 @@ fn execute() -> i32 {
152
171
"Recursively searches the given path for the rustfmt.toml config file. If not \
153
172
found reverts to the input file path",
154
173
"[Path for the configuration file]" ) ;
174
+ if file_lines_enabled ( ) {
175
+ opts. optmulti ( "" ,
176
+ "file-lines" ,
177
+ "Format specified line RANGEs in FILE. RANGEs are inclusive of both \
178
+ endpoints. May be specified multiple times.",
179
+ "FILE:RANGE,RANGE,..." ) ;
180
+ }
155
181
156
182
let matches = match opts. parse ( env:: args ( ) . skip ( 1 ) ) {
157
183
Ok ( m) => m,
@@ -228,6 +254,27 @@ fn execute() -> i32 {
228
254
}
229
255
0
230
256
}
257
+ // TODO: figure out what to do with config_path.
258
+ Operation :: FormatLineRanges { file_lines_map } => {
259
+ for ( file, line_ranges) in file_lines_map {
260
+ let ( mut config, config_path) = resolve_config ( file. parent ( ) . unwrap ( ) )
261
+ . expect ( & format ! ( "Error resolving config \
262
+ for {}",
263
+ file. display( ) ) ) ;
264
+ if let Some ( path) = config_path. as_ref ( ) {
265
+ println ! ( "Using rustfmt config file {} for {}" ,
266
+ path. display( ) ,
267
+ file. display( ) ) ;
268
+ }
269
+ if let Err ( e) = update_config ( & mut config, & matches) {
270
+ print_usage ( & opts, & e) ;
271
+ return 1 ;
272
+ }
273
+ config. line_ranges = line_ranges;
274
+ run ( & file, & config) ;
275
+ }
276
+ 0
277
+ }
231
278
}
232
279
}
233
280
@@ -283,6 +330,29 @@ fn determine_operation(matches: &Matches) -> Operation {
283
330
Some ( dir)
284
331
} ) ;
285
332
333
+ if file_lines_enabled ( ) && matches. opt_present ( "file-lines" ) {
334
+ let file_lines = matches. opt_strs ( "file-lines" ) ;
335
+ let mut file_lines_map = FileLinesMap :: new ( ) ;
336
+ for range_spec in file_lines {
337
+ let invalid = || {
338
+ Operation :: InvalidInput {
339
+ reason : format ! ( "invalid file-lines argument: {}" , range_spec) ,
340
+ }
341
+ } ;
342
+
343
+ let ( file, line_ranges) = match parse:: file_lines_arg ( & range_spec) {
344
+ IResult :: Error ( _) |
345
+ IResult :: Incomplete ( _) => return invalid ( ) ,
346
+ IResult :: Done ( remaining, _) if !remaining. is_empty ( ) => return invalid ( ) ,
347
+ IResult :: Done ( _, ( file, line_ranges) ) => ( file, line_ranges) ,
348
+ } ;
349
+
350
+ let entry = file_lines_map. entry ( file) . or_insert ( LineRanges ( Vec :: new ( ) ) ) ;
351
+ entry. 0 . extend ( line_ranges. 0 ) ;
352
+ }
353
+ return Operation :: FormatLineRanges { file_lines_map : file_lines_map } ;
354
+ }
355
+
286
356
// if no file argument is supplied, read from stdin
287
357
if matches. free . is_empty ( ) {
288
358
@@ -305,3 +375,59 @@ fn determine_operation(matches: &Matches) -> Operation {
305
375
config_path : config_path,
306
376
}
307
377
}
378
+
379
+
380
+ /// Parser for the `file-lines` argument.
381
+ mod parse {
382
+ use std:: path:: PathBuf ;
383
+ use std:: str:: FromStr ;
384
+ use rustfmt:: config:: { LineRange , LineRanges } ;
385
+
386
+ use nom:: digit;
387
+
388
+ named ! ( pub file_lines_arg<& str , ( PathBuf , LineRanges ) >,
389
+ chain!(
390
+ file: map!(
391
+ is_not_s!( ":" ) ,
392
+ PathBuf :: from
393
+ ) ~
394
+ tag_s!( ":" ) ~
395
+ line_ranges: line_ranges,
396
+ || ( file, line_ranges)
397
+ )
398
+ ) ;
399
+
400
+ named ! ( usize_digit<& str , usize >,
401
+ map_res!(
402
+ digit,
403
+ FromStr :: from_str
404
+ )
405
+ ) ;
406
+
407
+ named ! ( line_range<& str , LineRange >,
408
+ map_res!(
409
+ separated_pair!(
410
+ usize_digit,
411
+ tag_s!( "-" ) ,
412
+ usize_digit
413
+ ) ,
414
+ |pair| {
415
+ let ( lo, hi) = pair;
416
+ if lo < hi {
417
+ return Err ( format!( "empty line range: {}-{}" , lo, hi) ) ;
418
+ }
419
+ Ok ( LineRange { lo: lo, hi: hi } )
420
+ }
421
+ )
422
+ ) ;
423
+
424
+ named ! ( line_ranges<& str , LineRanges >,
425
+ map!(
426
+ separated_nonempty_list!(
427
+ tag_s!( "," ) ,
428
+ line_range
429
+ ) ,
430
+ LineRanges
431
+ )
432
+ ) ;
433
+ }
0 commit comments