]>
Commit | Line | Data |
---|---|---|
f20569fa | 1 | use anyhow::{format_err, Result}; |
cdc7bbd5 | 2 | |
f20569fa XL |
3 | use io::Error as IoError; |
4 | use thiserror::Error; | |
5 | ||
6 | use rustfmt_nightly as rustfmt; | |
7 | ||
8 | use std::collections::HashMap; | |
9 | use std::env; | |
10 | use std::fs::File; | |
11 | use std::io::{self, stdout, Read, Write}; | |
12 | use std::path::{Path, PathBuf}; | |
13 | use std::str::FromStr; | |
14 | ||
15 | use getopts::{Matches, Options}; | |
16 | ||
17 | use crate::rustfmt::{ | |
18 | load_config, CliOptions, Color, Config, Edition, EmitMode, FileLines, FileName, | |
19 | FormatReportFormatterBuilder, Input, Session, Verbosity, | |
20 | }; | |
21 | ||
22 | fn main() { | |
23 | env_logger::init(); | |
24 | let opts = make_opts(); | |
25 | ||
26 | let exit_code = match execute(&opts) { | |
27 | Ok(code) => code, | |
28 | Err(e) => { | |
29 | eprintln!("{}", e.to_string()); | |
30 | 1 | |
31 | } | |
32 | }; | |
33 | // Make sure standard output is flushed before we exit. | |
34 | std::io::stdout().flush().unwrap(); | |
35 | ||
36 | // Exit with given exit code. | |
37 | // | |
38 | // NOTE: this immediately terminates the process without doing any cleanup, | |
39 | // so make sure to finish all necessary cleanup before this is called. | |
40 | std::process::exit(exit_code); | |
41 | } | |
42 | ||
43 | /// Rustfmt operations. | |
44 | enum Operation { | |
45 | /// Format files and their child modules. | |
46 | Format { | |
47 | files: Vec<PathBuf>, | |
48 | minimal_config_path: Option<String>, | |
49 | }, | |
50 | /// Print the help message. | |
51 | Help(HelpOp), | |
52 | /// Print version information | |
53 | Version, | |
54 | /// Output default config to a file, or stdout if None | |
55 | ConfigOutputDefault { path: Option<String> }, | |
56 | /// Output current config (as if formatting to a file) to stdout | |
57 | ConfigOutputCurrent { path: Option<String> }, | |
58 | /// No file specified, read from stdin | |
59 | Stdin { input: String }, | |
60 | } | |
61 | ||
62 | /// Rustfmt operations errors. | |
63 | #[derive(Error, Debug)] | |
64 | pub enum OperationError { | |
65 | /// An unknown help topic was requested. | |
66 | #[error("Unknown help topic: `{0}`.")] | |
67 | UnknownHelpTopic(String), | |
68 | /// An unknown print-config option was requested. | |
69 | #[error("Unknown print-config option: `{0}`.")] | |
70 | UnknownPrintConfigTopic(String), | |
71 | /// Attempt to generate a minimal config from standard input. | |
72 | #[error("The `--print-config=minimal` option doesn't work with standard input.")] | |
73 | MinimalPathWithStdin, | |
74 | /// An io error during reading or writing. | |
75 | #[error("{0}")] | |
76 | IoError(IoError), | |
77 | /// Attempt to use --check with stdin, which isn't currently | |
78 | /// supported. | |
79 | #[error("The `--check` option is not supported with standard input.")] | |
80 | CheckWithStdin, | |
81 | /// Attempt to use --emit=json with stdin, which isn't currently | |
82 | /// supported. | |
83 | #[error("Using `--emit` other than stdout is not supported with standard input.")] | |
84 | EmitWithStdin, | |
85 | } | |
86 | ||
87 | impl From<IoError> for OperationError { | |
88 | fn from(e: IoError) -> OperationError { | |
89 | OperationError::IoError(e) | |
90 | } | |
91 | } | |
92 | ||
93 | /// Arguments to `--help` | |
94 | enum HelpOp { | |
95 | None, | |
96 | Config, | |
97 | FileLines, | |
98 | } | |
99 | ||
100 | fn make_opts() -> Options { | |
101 | let mut opts = Options::new(); | |
102 | ||
103 | opts.optflag( | |
104 | "", | |
105 | "check", | |
106 | "Run in 'check' mode. Exits with 0 if input is formatted correctly. Exits \ | |
107 | with 1 and prints a diff if formatting is required.", | |
108 | ); | |
109 | let is_nightly = is_nightly(); | |
110 | let emit_opts = if is_nightly { | |
111 | "[files|stdout|coverage|checkstyle|json]" | |
112 | } else { | |
113 | "[files|stdout]" | |
114 | }; | |
115 | opts.optopt("", "emit", "What data to emit and how", emit_opts); | |
116 | opts.optflag("", "backup", "Backup any modified files."); | |
117 | opts.optopt( | |
118 | "", | |
119 | "config-path", | |
120 | "Recursively searches the given path for the rustfmt.toml config file. If not \ | |
121 | found reverts to the input file path", | |
122 | "[Path for the configuration file]", | |
123 | ); | |
124 | opts.optopt("", "edition", "Rust edition to use", "[2015|2018]"); | |
125 | opts.optopt( | |
126 | "", | |
127 | "color", | |
128 | "Use colored output (if supported)", | |
129 | "[always|never|auto]", | |
130 | ); | |
131 | opts.optopt( | |
132 | "", | |
133 | "print-config", | |
134 | "Dumps a default or minimal config to PATH. A minimal config is the \ | |
135 | subset of the current config file used for formatting the current program. \ | |
136 | `current` writes to stdout current config as if formatting the file at PATH.", | |
137 | "[default|minimal|current] PATH", | |
138 | ); | |
139 | opts.optflag( | |
140 | "l", | |
141 | "files-with-diff", | |
142 | "Prints the names of mismatched files that were formatted. Prints the names of \ | |
143 | files that would be formated when used with `--check` mode. ", | |
144 | ); | |
145 | opts.optmulti( | |
146 | "", | |
147 | "config", | |
148 | "Set options from command line. These settings take priority over .rustfmt.toml", | |
149 | "[key1=val1,key2=val2...]", | |
150 | ); | |
151 | ||
152 | if is_nightly { | |
153 | opts.optflag( | |
154 | "", | |
155 | "unstable-features", | |
156 | "Enables unstable features. Only available on nightly channel.", | |
157 | ); | |
158 | opts.optopt( | |
159 | "", | |
160 | "file-lines", | |
161 | "Format specified line ranges. Run with `--help=file-lines` for \ | |
162 | more detail (unstable).", | |
163 | "JSON", | |
164 | ); | |
165 | opts.optflag( | |
166 | "", | |
167 | "error-on-unformatted", | |
168 | "Error if unable to get comments or string literals within max_width, \ | |
169 | or they are left with trailing whitespaces (unstable).", | |
170 | ); | |
171 | opts.optflag( | |
172 | "", | |
173 | "skip-children", | |
174 | "Don't reformat child modules (unstable).", | |
175 | ); | |
176 | } | |
177 | ||
178 | opts.optflag("v", "verbose", "Print verbose output"); | |
179 | opts.optflag("q", "quiet", "Print less output"); | |
180 | opts.optflag("V", "version", "Show version information"); | |
181 | opts.optflagopt( | |
182 | "h", | |
183 | "help", | |
184 | "Show this message or help about a specific topic: `config` or `file-lines`", | |
185 | "=TOPIC", | |
186 | ); | |
187 | ||
188 | opts | |
189 | } | |
190 | ||
191 | fn is_nightly() -> bool { | |
192 | option_env!("CFG_RELEASE_CHANNEL").map_or(true, |c| c == "nightly" || c == "dev") | |
193 | } | |
194 | ||
195 | // Returned i32 is an exit code | |
196 | fn execute(opts: &Options) -> Result<i32> { | |
197 | let matches = opts.parse(env::args().skip(1))?; | |
198 | let options = GetOptsOptions::from_matches(&matches)?; | |
199 | ||
200 | match determine_operation(&matches)? { | |
201 | Operation::Help(HelpOp::None) => { | |
202 | print_usage_to_stdout(opts, ""); | |
203 | Ok(0) | |
204 | } | |
205 | Operation::Help(HelpOp::Config) => { | |
206 | Config::print_docs(&mut stdout(), options.unstable_features); | |
207 | Ok(0) | |
208 | } | |
209 | Operation::Help(HelpOp::FileLines) => { | |
210 | print_help_file_lines(); | |
211 | Ok(0) | |
212 | } | |
213 | Operation::Version => { | |
214 | print_version(); | |
215 | Ok(0) | |
216 | } | |
217 | Operation::ConfigOutputDefault { path } => { | |
218 | let toml = Config::default().all_options().to_toml()?; | |
219 | if let Some(path) = path { | |
220 | let mut file = File::create(path)?; | |
221 | file.write_all(toml.as_bytes())?; | |
222 | } else { | |
223 | io::stdout().write_all(toml.as_bytes())?; | |
224 | } | |
225 | Ok(0) | |
226 | } | |
227 | Operation::ConfigOutputCurrent { path } => { | |
228 | let path = match path { | |
229 | Some(path) => path, | |
230 | None => return Err(format_err!("PATH required for `--print-config current`")), | |
231 | }; | |
232 | ||
233 | let file = PathBuf::from(path); | |
234 | let file = file.canonicalize().unwrap_or(file); | |
235 | ||
cdc7bbd5 | 236 | let (config, _) = load_config(Some(file.parent().unwrap()), Some(options))?; |
f20569fa XL |
237 | let toml = config.all_options().to_toml()?; |
238 | io::stdout().write_all(toml.as_bytes())?; | |
239 | ||
240 | Ok(0) | |
241 | } | |
242 | Operation::Stdin { input } => format_string(input, options), | |
243 | Operation::Format { | |
244 | files, | |
245 | minimal_config_path, | |
246 | } => format(files, minimal_config_path, &options), | |
247 | } | |
248 | } | |
249 | ||
250 | fn format_string(input: String, options: GetOptsOptions) -> Result<i32> { | |
251 | // try to read config from local directory | |
252 | let (mut config, _) = load_config(Some(Path::new(".")), Some(options.clone()))?; | |
253 | ||
254 | if options.check { | |
255 | return Err(OperationError::CheckWithStdin.into()); | |
256 | } | |
257 | if let Some(emit_mode) = options.emit_mode { | |
258 | if emit_mode != EmitMode::Stdout { | |
259 | return Err(OperationError::EmitWithStdin.into()); | |
260 | } | |
261 | } | |
262 | // emit mode is always Stdout for Stdin. | |
263 | config.set().emit_mode(EmitMode::Stdout); | |
264 | config.set().verbose(Verbosity::Quiet); | |
265 | ||
266 | // parse file_lines | |
267 | config.set().file_lines(options.file_lines); | |
268 | for f in config.file_lines().files() { | |
269 | match *f { | |
270 | FileName::Stdin => {} | |
271 | _ => eprintln!("Warning: Extra file listed in file_lines option '{}'", f), | |
272 | } | |
273 | } | |
274 | ||
275 | let out = &mut stdout(); | |
276 | let mut session = Session::new(config, Some(out)); | |
277 | format_and_emit_report(&mut session, Input::Text(input)); | |
278 | ||
279 | let exit_code = if session.has_operational_errors() || session.has_parsing_errors() { | |
280 | 1 | |
281 | } else { | |
282 | 0 | |
283 | }; | |
284 | Ok(exit_code) | |
285 | } | |
286 | ||
287 | fn format( | |
288 | files: Vec<PathBuf>, | |
289 | minimal_config_path: Option<String>, | |
290 | options: &GetOptsOptions, | |
291 | ) -> Result<i32> { | |
292 | options.verify_file_lines(&files); | |
293 | let (config, config_path) = load_config(None, Some(options.clone()))?; | |
294 | ||
295 | if config.verbose() == Verbosity::Verbose { | |
296 | if let Some(path) = config_path.as_ref() { | |
297 | println!("Using rustfmt config file {}", path.display()); | |
298 | } | |
299 | } | |
300 | ||
301 | let out = &mut stdout(); | |
302 | let mut session = Session::new(config, Some(out)); | |
303 | ||
304 | for file in files { | |
305 | if !file.exists() { | |
306 | eprintln!("Error: file `{}` does not exist", file.to_str().unwrap()); | |
307 | session.add_operational_error(); | |
308 | } else if file.is_dir() { | |
309 | eprintln!("Error: `{}` is a directory", file.to_str().unwrap()); | |
310 | session.add_operational_error(); | |
311 | } else { | |
312 | // Check the file directory if the config-path could not be read or not provided | |
313 | if config_path.is_none() { | |
314 | let (local_config, config_path) = | |
315 | load_config(Some(file.parent().unwrap()), Some(options.clone()))?; | |
316 | if local_config.verbose() == Verbosity::Verbose { | |
317 | if let Some(path) = config_path { | |
318 | println!( | |
319 | "Using rustfmt config file {} for {}", | |
320 | path.display(), | |
321 | file.display() | |
322 | ); | |
323 | } | |
324 | } | |
325 | ||
326 | session.override_config(local_config, |sess| { | |
327 | format_and_emit_report(sess, Input::File(file)) | |
328 | }); | |
329 | } else { | |
330 | format_and_emit_report(&mut session, Input::File(file)); | |
331 | } | |
332 | } | |
333 | } | |
334 | ||
335 | // If we were given a path via dump-minimal-config, output any options | |
336 | // that were used during formatting as TOML. | |
337 | if let Some(path) = minimal_config_path { | |
338 | let mut file = File::create(path)?; | |
339 | let toml = session.config.used_options().to_toml()?; | |
340 | file.write_all(toml.as_bytes())?; | |
341 | } | |
342 | ||
343 | let exit_code = if session.has_operational_errors() | |
344 | || session.has_parsing_errors() | |
345 | || ((session.has_diff() || session.has_check_errors()) && options.check) | |
346 | { | |
347 | 1 | |
348 | } else { | |
349 | 0 | |
350 | }; | |
351 | Ok(exit_code) | |
352 | } | |
353 | ||
354 | fn format_and_emit_report<T: Write>(session: &mut Session<'_, T>, input: Input) { | |
355 | match session.format(input) { | |
356 | Ok(report) => { | |
357 | if report.has_warnings() { | |
358 | eprintln!( | |
359 | "{}", | |
360 | FormatReportFormatterBuilder::new(&report) | |
361 | .enable_colors(should_print_with_colors(session)) | |
362 | .build() | |
363 | ); | |
364 | } | |
365 | } | |
366 | Err(msg) => { | |
367 | eprintln!("Error writing files: {}", msg); | |
368 | session.add_operational_error(); | |
369 | } | |
370 | } | |
371 | } | |
372 | ||
373 | fn should_print_with_colors<T: Write>(session: &mut Session<'_, T>) -> bool { | |
374 | match term::stderr() { | |
375 | Some(ref t) | |
376 | if session.config.color().use_colored_tty() | |
377 | && t.supports_color() | |
378 | && t.supports_attr(term::Attr::Bold) => | |
379 | { | |
380 | true | |
381 | } | |
382 | _ => false, | |
383 | } | |
384 | } | |
385 | ||
386 | fn print_usage_to_stdout(opts: &Options, reason: &str) { | |
387 | let sep = if reason.is_empty() { | |
388 | String::new() | |
389 | } else { | |
390 | format!("{}\n\n", reason) | |
391 | }; | |
392 | let msg = format!( | |
393 | "{}Format Rust code\n\nusage: {} [options] <file>...", | |
394 | sep, | |
395 | env::args_os().next().unwrap().to_string_lossy() | |
396 | ); | |
397 | println!("{}", opts.usage(&msg)); | |
398 | } | |
399 | ||
400 | fn print_help_file_lines() { | |
401 | println!( | |
402 | "If you want to restrict reformatting to specific sets of lines, you can | |
403 | use the `--file-lines` option. Its argument is a JSON array of objects | |
404 | with `file` and `range` properties, where `file` is a file name, and | |
405 | `range` is an array representing a range of lines like `[7,13]`. Ranges | |
406 | are 1-based and inclusive of both end points. Specifying an empty array | |
407 | will result in no files being formatted. For example, | |
408 | ||
409 | ``` | |
410 | rustfmt --file-lines '[ | |
411 | {{\"file\":\"src/lib.rs\",\"range\":[7,13]}}, | |
412 | {{\"file\":\"src/lib.rs\",\"range\":[21,29]}}, | |
413 | {{\"file\":\"src/foo.rs\",\"range\":[10,11]}}, | |
414 | {{\"file\":\"src/foo.rs\",\"range\":[15,15]}}]' | |
415 | ``` | |
416 | ||
417 | would format lines `7-13` and `21-29` of `src/lib.rs`, and lines `10-11`, | |
418 | and `15` of `src/foo.rs`. No other files would be formatted, even if they | |
419 | are included as out of line modules from `src/lib.rs`." | |
420 | ); | |
421 | } | |
422 | ||
423 | fn print_version() { | |
424 | let version_info = format!( | |
425 | "{}-{}", | |
426 | option_env!("CARGO_PKG_VERSION").unwrap_or("unknown"), | |
427 | include_str!(concat!(env!("OUT_DIR"), "/commit-info.txt")) | |
428 | ); | |
429 | ||
430 | println!("rustfmt {}", version_info); | |
431 | } | |
432 | ||
433 | fn determine_operation(matches: &Matches) -> Result<Operation, OperationError> { | |
434 | if matches.opt_present("h") { | |
435 | let topic = matches.opt_str("h"); | |
436 | if topic == None { | |
437 | return Ok(Operation::Help(HelpOp::None)); | |
438 | } else if topic == Some("config".to_owned()) { | |
439 | return Ok(Operation::Help(HelpOp::Config)); | |
440 | } else if topic == Some("file-lines".to_owned()) { | |
441 | return Ok(Operation::Help(HelpOp::FileLines)); | |
442 | } else { | |
443 | return Err(OperationError::UnknownHelpTopic(topic.unwrap())); | |
444 | } | |
445 | } | |
446 | let mut free_matches = matches.free.iter(); | |
447 | ||
448 | let mut minimal_config_path = None; | |
449 | if let Some(kind) = matches.opt_str("print-config") { | |
450 | let path = free_matches.next().cloned(); | |
451 | match kind.as_str() { | |
452 | "default" => return Ok(Operation::ConfigOutputDefault { path }), | |
453 | "current" => return Ok(Operation::ConfigOutputCurrent { path }), | |
454 | "minimal" => { | |
455 | minimal_config_path = path; | |
456 | if minimal_config_path.is_none() { | |
457 | eprintln!("WARNING: PATH required for `--print-config minimal`."); | |
458 | } | |
459 | } | |
460 | _ => { | |
461 | return Err(OperationError::UnknownPrintConfigTopic(kind)); | |
462 | } | |
463 | } | |
464 | } | |
465 | ||
466 | if matches.opt_present("version") { | |
467 | return Ok(Operation::Version); | |
468 | } | |
469 | ||
470 | let files: Vec<_> = free_matches | |
471 | .map(|s| { | |
472 | let p = PathBuf::from(s); | |
473 | // we will do comparison later, so here tries to canonicalize first | |
474 | // to get the expected behavior. | |
475 | p.canonicalize().unwrap_or(p) | |
476 | }) | |
477 | .collect(); | |
478 | ||
479 | // if no file argument is supplied, read from stdin | |
480 | if files.is_empty() { | |
481 | if minimal_config_path.is_some() { | |
482 | return Err(OperationError::MinimalPathWithStdin); | |
483 | } | |
484 | let mut buffer = String::new(); | |
485 | io::stdin().read_to_string(&mut buffer)?; | |
486 | ||
487 | return Ok(Operation::Stdin { input: buffer }); | |
488 | } | |
489 | ||
490 | Ok(Operation::Format { | |
491 | files, | |
492 | minimal_config_path, | |
493 | }) | |
494 | } | |
495 | ||
496 | const STABLE_EMIT_MODES: [EmitMode; 3] = [EmitMode::Files, EmitMode::Stdout, EmitMode::Diff]; | |
497 | ||
498 | /// Parsed command line options. | |
499 | #[derive(Clone, Debug, Default)] | |
500 | struct GetOptsOptions { | |
501 | skip_children: Option<bool>, | |
502 | quiet: bool, | |
503 | verbose: bool, | |
504 | config_path: Option<PathBuf>, | |
505 | inline_config: HashMap<String, String>, | |
506 | emit_mode: Option<EmitMode>, | |
507 | backup: bool, | |
508 | check: bool, | |
509 | edition: Option<Edition>, | |
510 | color: Option<Color>, | |
511 | file_lines: FileLines, // Default is all lines in all files. | |
512 | unstable_features: bool, | |
513 | error_on_unformatted: Option<bool>, | |
514 | print_misformatted_file_names: bool, | |
515 | } | |
516 | ||
517 | impl GetOptsOptions { | |
518 | pub fn from_matches(matches: &Matches) -> Result<GetOptsOptions> { | |
519 | let mut options = GetOptsOptions::default(); | |
520 | options.verbose = matches.opt_present("verbose"); | |
521 | options.quiet = matches.opt_present("quiet"); | |
522 | if options.verbose && options.quiet { | |
523 | return Err(format_err!("Can't use both `--verbose` and `--quiet`")); | |
524 | } | |
525 | ||
526 | let rust_nightly = is_nightly(); | |
527 | ||
528 | if rust_nightly { | |
529 | options.unstable_features = matches.opt_present("unstable-features"); | |
530 | ||
531 | if options.unstable_features { | |
532 | if matches.opt_present("skip-children") { | |
533 | options.skip_children = Some(true); | |
534 | } | |
535 | if matches.opt_present("error-on-unformatted") { | |
536 | options.error_on_unformatted = Some(true); | |
537 | } | |
538 | if let Some(ref file_lines) = matches.opt_str("file-lines") { | |
539 | options.file_lines = file_lines.parse()?; | |
540 | } | |
541 | } else { | |
542 | let mut unstable_options = vec![]; | |
543 | if matches.opt_present("skip-children") { | |
544 | unstable_options.push("`--skip-children`"); | |
545 | } | |
546 | if matches.opt_present("error-on-unformatted") { | |
547 | unstable_options.push("`--error-on-unformatted`"); | |
548 | } | |
549 | if matches.opt_present("file-lines") { | |
550 | unstable_options.push("`--file-lines`"); | |
551 | } | |
552 | if !unstable_options.is_empty() { | |
553 | let s = if unstable_options.len() == 1 { "" } else { "s" }; | |
554 | return Err(format_err!( | |
555 | "Unstable option{} ({}) used without `--unstable-features`", | |
556 | s, | |
557 | unstable_options.join(", "), | |
558 | )); | |
559 | } | |
560 | } | |
561 | } | |
562 | ||
563 | options.config_path = matches.opt_str("config-path").map(PathBuf::from); | |
564 | ||
565 | options.inline_config = matches | |
566 | .opt_strs("config") | |
567 | .iter() | |
cdc7bbd5 | 568 | .flat_map(|config| config.split(',')) |
f20569fa XL |
569 | .map( |
570 | |key_val| match key_val.char_indices().find(|(_, ch)| *ch == '=') { | |
571 | Some((middle, _)) => { | |
572 | let (key, val) = (&key_val[..middle], &key_val[middle + 1..]); | |
573 | if !Config::is_valid_key_val(key, val) { | |
574 | Err(format_err!("invalid key=val pair: `{}`", key_val)) | |
575 | } else { | |
576 | Ok((key.to_string(), val.to_string())) | |
577 | } | |
578 | } | |
579 | ||
580 | None => Err(format_err!( | |
581 | "--config expects comma-separated list of key=val pairs, found `{}`", | |
582 | key_val | |
583 | )), | |
584 | }, | |
585 | ) | |
586 | .collect::<Result<HashMap<_, _>, _>>()?; | |
587 | ||
588 | options.check = matches.opt_present("check"); | |
589 | if let Some(ref emit_str) = matches.opt_str("emit") { | |
590 | if options.check { | |
591 | return Err(format_err!("Invalid to use `--emit` and `--check`")); | |
592 | } | |
593 | ||
594 | options.emit_mode = Some(emit_mode_from_emit_str(emit_str)?); | |
595 | } | |
596 | ||
597 | if let Some(ref edition_str) = matches.opt_str("edition") { | |
598 | options.edition = Some(edition_from_edition_str(edition_str)?); | |
599 | } | |
600 | ||
601 | if matches.opt_present("backup") { | |
602 | options.backup = true; | |
603 | } | |
604 | ||
605 | if matches.opt_present("files-with-diff") { | |
606 | options.print_misformatted_file_names = true; | |
607 | } | |
608 | ||
609 | if !rust_nightly { | |
610 | if let Some(ref emit_mode) = options.emit_mode { | |
611 | if !STABLE_EMIT_MODES.contains(emit_mode) { | |
612 | return Err(format_err!( | |
613 | "Invalid value for `--emit` - using an unstable \ | |
614 | value without `--unstable-features`", | |
615 | )); | |
616 | } | |
617 | } | |
618 | } | |
619 | ||
620 | if let Some(ref color) = matches.opt_str("color") { | |
621 | match Color::from_str(color) { | |
622 | Ok(color) => options.color = Some(color), | |
623 | _ => return Err(format_err!("Invalid color: {}", color)), | |
624 | } | |
625 | } | |
626 | ||
627 | Ok(options) | |
628 | } | |
629 | ||
630 | fn verify_file_lines(&self, files: &[PathBuf]) { | |
631 | for f in self.file_lines.files() { | |
632 | match *f { | |
633 | FileName::Real(ref f) if files.contains(f) => {} | |
634 | FileName::Real(_) => { | |
635 | eprintln!("Warning: Extra file listed in file_lines option '{}'", f) | |
636 | } | |
637 | FileName::Stdin => eprintln!("Warning: Not a file '{}'", f), | |
638 | } | |
639 | } | |
640 | } | |
641 | } | |
642 | ||
643 | impl CliOptions for GetOptsOptions { | |
644 | fn apply_to(self, config: &mut Config) { | |
645 | if self.verbose { | |
646 | config.set().verbose(Verbosity::Verbose); | |
647 | } else if self.quiet { | |
648 | config.set().verbose(Verbosity::Quiet); | |
649 | } else { | |
650 | config.set().verbose(Verbosity::Normal); | |
651 | } | |
652 | config.set().file_lines(self.file_lines); | |
653 | config.set().unstable_features(self.unstable_features); | |
654 | if let Some(skip_children) = self.skip_children { | |
655 | config.set().skip_children(skip_children); | |
656 | } | |
657 | if let Some(error_on_unformatted) = self.error_on_unformatted { | |
658 | config.set().error_on_unformatted(error_on_unformatted); | |
659 | } | |
660 | if let Some(edition) = self.edition { | |
661 | config.set().edition(edition); | |
662 | } | |
663 | if self.check { | |
664 | config.set().emit_mode(EmitMode::Diff); | |
665 | } else if let Some(emit_mode) = self.emit_mode { | |
666 | config.set().emit_mode(emit_mode); | |
667 | } | |
668 | if self.backup { | |
669 | config.set().make_backup(true); | |
670 | } | |
671 | if let Some(color) = self.color { | |
672 | config.set().color(color); | |
673 | } | |
674 | if self.print_misformatted_file_names { | |
675 | config.set().print_misformatted_file_names(true); | |
676 | } | |
677 | ||
678 | for (key, val) in self.inline_config { | |
679 | config.override_value(&key, &val); | |
680 | } | |
681 | } | |
682 | ||
683 | fn config_path(&self) -> Option<&Path> { | |
cdc7bbd5 | 684 | self.config_path.as_deref() |
f20569fa XL |
685 | } |
686 | } | |
687 | ||
688 | fn edition_from_edition_str(edition_str: &str) -> Result<Edition> { | |
689 | match edition_str { | |
690 | "2015" => Ok(Edition::Edition2015), | |
691 | "2018" => Ok(Edition::Edition2018), | |
692 | _ => Err(format_err!("Invalid value for `--edition`")), | |
693 | } | |
694 | } | |
695 | ||
696 | fn emit_mode_from_emit_str(emit_str: &str) -> Result<EmitMode> { | |
697 | match emit_str { | |
698 | "files" => Ok(EmitMode::Files), | |
699 | "stdout" => Ok(EmitMode::Stdout), | |
700 | "coverage" => Ok(EmitMode::Coverage), | |
701 | "checkstyle" => Ok(EmitMode::Checkstyle), | |
702 | "json" => Ok(EmitMode::Json), | |
703 | _ => Err(format_err!("Invalid value for `--emit`")), | |
704 | } | |
705 | } |