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 } ;
@@ -42,6 +48,10 @@ enum Operation {
42
48
InvalidInput ( String ) ,
43
49
/// No file specified, read from stdin
44
50
Stdin ( String , Option < PathBuf > ) ,
51
+ /// Format a set of line ranges.
52
+ FormatLineRanges {
53
+ file_lines_map : FileLinesMap ,
54
+ } ,
45
55
}
46
56
47
57
/// Try to find a project file in the given directory and its parents. Returns the path of a the
@@ -112,7 +122,9 @@ fn match_cli_path_or_file(config_path: Option<PathBuf>,
112
122
113
123
fn update_config ( config : & mut Config , matches : & Matches ) -> Result < ( ) , String > {
114
124
config. verbose = matches. opt_present ( "verbose" ) ;
115
- config. skip_children = matches. opt_present ( "skip-children" ) ;
125
+ // `file-lines` implies `skip-children`.
126
+ config. skip_children = matches. opt_present ( "skip-children" ) ||
127
+ matches. opt_present ( "file-lines" ) ;
116
128
117
129
let write_mode = matches. opt_str ( "write-mode" ) ;
118
130
match matches. opt_str ( "write-mode" ) . map ( |wm| WriteMode :: from_str ( & wm) ) {
@@ -144,6 +156,13 @@ fn execute() -> i32 {
144
156
"Recursively searches the given path for the rustfmt.toml config file. If not \
145
157
found reverts to the input file path",
146
158
"[Path for the configuration file]" ) ;
159
+ if cfg ! ( feature = "experimental-file-lines" ) {
160
+ opts. optmulti ( "" ,
161
+ "file-lines" ,
162
+ "Format specified line RANGEs in FILENAME. RANGEs are inclusive of both \
163
+ endpoints. May be specified multiple times.",
164
+ "FILENAME:RANGE,RANGE,..." ) ;
165
+ }
147
166
148
167
let matches = match opts. parse ( env:: args ( ) . skip ( 1 ) ) {
149
168
Ok ( m) => m,
@@ -220,6 +239,27 @@ fn execute() -> i32 {
220
239
}
221
240
0
222
241
}
242
+ // TODO: figure out what to do with config_path.
243
+ Operation :: FormatLineRanges { file_lines_map } => {
244
+ for ( file, line_ranges) in file_lines_map {
245
+ let ( mut config, config_path) = resolve_config ( file. parent ( ) . unwrap ( ) )
246
+ . expect ( & format ! ( "Error resolving config \
247
+ for {}",
248
+ file. display( ) ) ) ;
249
+ if let Some ( path) = config_path. as_ref ( ) {
250
+ println ! ( "Using rustfmt config file {} for {}" ,
251
+ path. display( ) ,
252
+ file. display( ) ) ;
253
+ }
254
+ if let Err ( e) = update_config ( & mut config, & matches) {
255
+ print_usage ( & opts, & e) ;
256
+ return 1 ;
257
+ }
258
+ config. line_ranges = line_ranges;
259
+ run ( & file, & config) ;
260
+ }
261
+ 0
262
+ }
223
263
}
224
264
}
225
265
@@ -275,6 +315,27 @@ fn determine_operation(matches: &Matches) -> Operation {
275
315
Some ( dir)
276
316
} ) ;
277
317
318
+ if matches. opt_present ( "file-lines" ) {
319
+ let file_lines = matches. opt_strs ( "file-lines" ) ;
320
+ let mut file_lines_map = FileLinesMap :: new ( ) ;
321
+ for range_spec in file_lines {
322
+ let invalid = || {
323
+ Operation :: InvalidInput ( format ! ( "invalid file-lines argument: {}" , range_spec) )
324
+ } ;
325
+
326
+ let ( file, line_ranges) = match parse:: file_lines_arg ( & range_spec) {
327
+ IResult :: Error ( _) |
328
+ IResult :: Incomplete ( _) => return invalid ( ) ,
329
+ IResult :: Done ( remaining, _) if !remaining. is_empty ( ) => return invalid ( ) ,
330
+ IResult :: Done ( _, ( file, line_ranges) ) => ( file, line_ranges) ,
331
+ } ;
332
+
333
+ let entry = file_lines_map. entry ( file) . or_insert ( LineRanges ( Vec :: new ( ) ) ) ;
334
+ entry. 0 . extend ( line_ranges. 0 ) ;
335
+ }
336
+ return Operation :: FormatLineRanges { file_lines_map : file_lines_map } ;
337
+ }
338
+
278
339
// if no file argument is supplied, read from stdin
279
340
if matches. free . is_empty ( ) {
280
341
@@ -291,3 +352,59 @@ fn determine_operation(matches: &Matches) -> Operation {
291
352
292
353
Operation :: Format ( files, config_path)
293
354
}
355
+
356
+
357
+ /// Parser for the `file-lines` argument.
358
+ mod parse {
359
+ use std:: path:: PathBuf ;
360
+ use std:: str:: FromStr ;
361
+ use rustfmt:: config:: { LineRange , LineRanges } ;
362
+
363
+ use nom:: digit;
364
+
365
+ named ! ( pub file_lines_arg<& str , ( PathBuf , LineRanges ) >,
366
+ chain!(
367
+ file: map!(
368
+ is_not_s!( ":" ) ,
369
+ PathBuf :: from
370
+ ) ~
371
+ tag_s!( ":" ) ~
372
+ line_ranges: line_ranges,
373
+ || ( file, line_ranges)
374
+ )
375
+ ) ;
376
+
377
+ named ! ( usize_digit<& str , usize >,
378
+ map_res!(
379
+ digit,
380
+ FromStr :: from_str
381
+ )
382
+ ) ;
383
+
384
+ named ! ( line_range<& str , LineRange >,
385
+ map_res!(
386
+ separated_pair!(
387
+ usize_digit,
388
+ tag_s!( "-" ) ,
389
+ usize_digit
390
+ ) ,
391
+ |pair: ( usize , usize ) | {
392
+ let ( start, end) = pair;
393
+ if end < start {
394
+ return Err ( format!( "empty line range: {}-{}" , start, end) ) ;
395
+ }
396
+ Ok ( pair)
397
+ }
398
+ )
399
+ ) ;
400
+
401
+ named ! ( line_ranges<& str , LineRanges >,
402
+ map!(
403
+ separated_nonempty_list!(
404
+ tag_s!( "," ) ,
405
+ line_range
406
+ ) ,
407
+ LineRanges
408
+ )
409
+ ) ;
410
+ }
0 commit comments