]> git.lizzy.rs Git - rust.git/blobdiff - src/bin/main.rs
add new flag to list names of misformatted files (#3747)
[rust.git] / src / bin / main.rs
index dba319f44af32f89219e6857590ed5cb4daf6203..bb12169329a1e079a1ad601ecfca9f9af674639b 100644 (file)
@@ -1,32 +1,20 @@
-// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
-// file at the top-level directory of this distribution and at
-// http://rust-lang.org/COPYRIGHT.
-//
-// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
-// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
-// option. This file may not be copied, modified, or distributed
-// except according to those terms.
-
-#![cfg(not(test))]
-
-extern crate env_logger;
-extern crate failure;
-extern crate getopts;
-extern crate rustfmt_nightly as rustfmt;
+use env_logger;
+use failure::{err_msg, format_err, Error as FailureError, Fail};
+use io::Error as IoError;
+
+use rustfmt_nightly as rustfmt;
 
 use std::env;
 use std::fs::File;
 use std::io::{self, stdout, Read, Write};
 use std::path::{Path, PathBuf};
-
-use failure::err_msg;
+use std::str::FromStr;
 
 use getopts::{Matches, Options};
 
-use rustfmt::{
-    emit_post_matter, emit_pre_matter, format_and_emit_report, load_config, CliOptions, Config,
-    FileName, FmtResult, Input, Summary, Verbosity, WriteMode, WRITE_MODE_LIST,
+use crate::rustfmt::{
+    load_config, CliOptions, Color, Config, Edition, EmitMode, FileLines, FileName,
+    FormatReportFormatterBuilder, Input, Session, Verbosity,
 };
 
 fn main() {
@@ -34,16 +22,7 @@ fn main() {
     let opts = make_opts();
 
     let exit_code = match execute(&opts) {
-        Ok((write_mode, summary)) => {
-            if summary.has_operational_errors()
-                || summary.has_parsing_errors()
-                || (summary.has_diff && write_mode == WriteMode::Check)
-            {
-                1
-            } else {
-                0
-            }
-        }
+        Ok(code) => code,
         Err(e) => {
             eprintln!("{}", e.to_string());
             1
@@ -54,7 +33,7 @@ fn main() {
 
     // Exit with given exit code.
     //
-    // NOTE: This immediately terminates the process without doing any cleanup,
+    // NOTE: this immediately terminates the process without doing any cleanup,
     // so make sure to finish all necessary cleanup before this is called.
     std::process::exit(exit_code);
 }
@@ -67,36 +46,64 @@ enum Operation {
         minimal_config_path: Option<String>,
     },
     /// Print the help message.
-    Help,
-    // Print version information
+    Help(HelpOp),
+    /// Print version information
     Version,
-    /// Print detailed configuration help.
-    ConfigHelp,
     /// Output default config to a file, or stdout if None
-    ConfigOutputDefault {
-        path: Option<String>,
-    },
+    ConfigOutputDefault { path: Option<String> },
+    /// Output current config (as if formatting to a file) to stdout
+    ConfigOutputCurrent { path: Option<String> },
     /// No file specified, read from stdin
-    Stdin {
-        input: String,
-    },
+    Stdin { input: String },
+}
+
+/// Rustfmt operations errors.
+#[derive(Fail, Debug)]
+pub enum OperationError {
+    /// An unknown help topic was requested.
+    #[fail(display = "Unknown help topic: `{}`.", _0)]
+    UnknownHelpTopic(String),
+    /// An unknown print-config option was requested.
+    #[fail(display = "Unknown print-config option: `{}`.", _0)]
+    UnknownPrintConfigTopic(String),
+    /// Attempt to generate a minimal config from standard input.
+    #[fail(display = "The `--print-config=minimal` option doesn't work with standard input.")]
+    MinimalPathWithStdin,
+    /// An io error during reading or writing.
+    #[fail(display = "io error: {}", _0)]
+    IoError(IoError),
+}
+
+impl From<IoError> for OperationError {
+    fn from(e: IoError) -> OperationError {
+        OperationError::IoError(e)
+    }
+}
+
+/// Arguments to `--help`
+enum HelpOp {
+    None,
+    Config,
+    FileLines,
 }
 
 fn make_opts() -> Options {
     let mut opts = Options::new();
 
-    // Sorted in alphabetical order.
-    opts.optopt(
-        "",
-        "color",
-        "Use colored output (if supported)",
-        "[always|never|auto]",
-    );
     opts.optflag(
         "",
-        "config-help",
-        "Show details of rustfmt configuration options",
+        "check",
+        "Run in 'check' mode. Exits with 0 if input is formatted correctly. Exits \
+         with 1 and prints a diff if formatting is required.",
     );
+    let is_nightly = is_nightly();
+    let emit_opts = if is_nightly {
+        "[files|stdout|coverage|checkstyle|json]"
+    } else {
+        "[files|stdout]"
+    };
+    opts.optopt("", "emit", "What data to emit and how", emit_opts);
+    opts.optflag("", "backup", "Backup any modified files.");
     opts.optopt(
         "",
         "config-path",
@@ -104,68 +111,92 @@ fn make_opts() -> Options {
          found reverts to the input file path",
         "[Path for the configuration file]",
     );
-    opts.opt(
-        "",
-        "dump-default-config",
-        "Dumps default configuration to PATH. PATH defaults to stdout, if omitted.",
-        "PATH",
-        getopts::HasArg::Maybe,
-        getopts::Occur::Optional,
-    );
+    opts.optopt("", "edition", "Rust edition to use", "[2015|2018]");
     opts.optopt(
         "",
-        "dump-minimal-config",
-        "Dumps configuration options that were checked during formatting to a file.",
-        "PATH",
-    );
-    opts.optflag(
-        "",
-        "error-on-unformatted",
-        "Error if unable to get comments or string literals within max_width, \
-         or they are left with trailing whitespaces",
+        "color",
+        "Use colored output (if supported)",
+        "[always|never|auto]",
     );
     opts.optopt(
         "",
-        "file-lines",
-        "Format specified line ranges. See README for more detail on the JSON format.",
-        "JSON",
+        "print-config",
+        "Dumps a default or minimal config to PATH. A minimal config is the \
+         subset of the current config file used for formatting the current program. \
+         `current` writes to stdout current config as if formatting the file at PATH.",
+        "[default|minimal|current] PATH",
     );
-    opts.optflag("h", "help", "Show this message");
-    opts.optflag("", "skip-children", "Don't reformat child modules");
     opts.optflag(
-        "",
-        "unstable-features",
-        "Enables unstable features. Only available on nightly channel",
+        "l",
+        "files-with-diff",
+        "Prints the names of mismatched files that were formatted. Prints the names of \
+         files that would be formated when used with `--check` mode. ",
     );
+
+    if is_nightly {
+        opts.optflag(
+            "",
+            "unstable-features",
+            "Enables unstable features. Only available on nightly channel.",
+        );
+        opts.optopt(
+            "",
+            "file-lines",
+            "Format specified line ranges. Run with `--help=file-lines` for \
+             more detail (unstable).",
+            "JSON",
+        );
+        opts.optflag(
+            "",
+            "error-on-unformatted",
+            "Error if unable to get comments or string literals within max_width, \
+             or they are left with trailing whitespaces (unstable).",
+        );
+        opts.optflag(
+            "",
+            "skip-children",
+            "Don't reformat child modules (unstable).",
+        );
+    }
+
     opts.optflag("v", "verbose", "Print verbose output");
     opts.optflag("q", "quiet", "Print less output");
     opts.optflag("V", "version", "Show version information");
-    opts.optopt(
-        "",
-        "write-mode",
-        "How to write output (not usable when piping from stdin)",
-        WRITE_MODE_LIST,
+    opts.optflagopt(
+        "h",
+        "help",
+        "Show this message or help about a specific topic: `config` or `file-lines`",
+        "=TOPIC",
     );
 
     opts
 }
 
-fn execute(opts: &Options) -> FmtResult<(WriteMode, Summary)> {
+fn is_nightly() -> bool {
+    option_env!("CFG_RELEASE_CHANNEL").map_or(true, |c| c == "nightly" || c == "dev")
+}
+
+// Returned i32 is an exit code
+fn execute(opts: &Options) -> Result<i32, FailureError> {
     let matches = opts.parse(env::args().skip(1))?;
+    let options = GetOptsOptions::from_matches(&matches)?;
 
     match determine_operation(&matches)? {
-        Operation::Help => {
+        Operation::Help(HelpOp::None) => {
             print_usage_to_stdout(opts, "");
-            Summary::print_exit_codes();
-            Ok((WriteMode::None, Summary::default()))
+            Ok(0)
+        }
+        Operation::Help(HelpOp::Config) => {
+            Config::print_docs(&mut stdout(), options.unstable_features);
+            Ok(0)
+        }
+        Operation::Help(HelpOp::FileLines) => {
+            print_help_file_lines();
+            Ok(0)
         }
         Operation::Version => {
             print_version();
-            Ok((WriteMode::None, Summary::default()))
-        }
-        Operation::ConfigHelp => {
-            Config::print_docs(&mut stdout(), matches.opt_present("unstable-features"));
-            Ok((WriteMode::None, Summary::default()))
+            Ok(0)
         }
         Operation::ConfigOutputDefault { path } => {
             let toml = Config::default().all_options().to_toml().map_err(err_msg)?;
@@ -175,57 +206,67 @@ fn execute(opts: &Options) -> FmtResult<(WriteMode, Summary)> {
             } else {
                 io::stdout().write_all(toml.as_bytes())?;
             }
-            Ok((WriteMode::None, Summary::default()))
+            Ok(0)
         }
-        Operation::Stdin { input } => {
-            // try to read config from local directory
-            let options = CliOptions::from_matches(&matches)?;
-            let (mut config, _) = load_config(Some(Path::new(".")), Some(&options))?;
-
-            // write_mode is always Display for Stdin.
-            config.set().write_mode(WriteMode::Display);
-            config.set().verbose(Verbosity::Quiet);
+        Operation::ConfigOutputCurrent { path } => {
+            let path = match path {
+                Some(path) => path,
+                None => return Err(format_err!("PATH required for `--print-config current`")),
+            };
 
-            // parse file_lines
-            if let Some(ref file_lines) = matches.opt_str("file-lines") {
-                config
-                    .set()
-                    .file_lines(file_lines.parse().map_err(err_msg)?);
-                for f in config.file_lines().files() {
-                    match *f {
-                        FileName::Custom(ref f) if f == "stdin" => {}
-                        _ => eprintln!("Warning: Extra file listed in file_lines option '{}'", f),
-                    }
-                }
-            }
+            let file = PathBuf::from(path);
+            let file = file.canonicalize().unwrap_or(file);
 
-            let mut error_summary = Summary::default();
-            emit_pre_matter(&config)?;
-            match format_and_emit_report(Input::Text(input), &config) {
-                Ok(summary) => error_summary.add(summary),
-                Err(_) => error_summary.add_operational_error(),
-            }
-            emit_post_matter(&config)?;
+            let (config, _) = load_config(Some(file.parent().unwrap()), Some(options.clone()))?;
+            let toml = config.all_options().to_toml().map_err(err_msg)?;
+            io::stdout().write_all(toml.as_bytes())?;
 
-            Ok((WriteMode::Display, error_summary))
+            Ok(0)
         }
+        Operation::Stdin { input } => format_string(input, options),
         Operation::Format {
             files,
             minimal_config_path,
-        } => {
-            let options = CliOptions::from_matches(&matches)?;
-            format(files, minimal_config_path, options)
+        } => format(files, minimal_config_path, &options),
+    }
+}
+
+fn format_string(input: String, options: GetOptsOptions) -> Result<i32, FailureError> {
+    // try to read config from local directory
+    let (mut config, _) = load_config(Some(Path::new(".")), Some(options.clone()))?;
+
+    // emit mode is always Stdout for Stdin.
+    config.set().emit_mode(EmitMode::Stdout);
+    config.set().verbose(Verbosity::Quiet);
+
+    // parse file_lines
+    config.set().file_lines(options.file_lines);
+    for f in config.file_lines().files() {
+        match *f {
+            FileName::Stdin => {}
+            _ => eprintln!("Warning: Extra file listed in file_lines option '{}'", f),
         }
     }
+
+    let out = &mut stdout();
+    let mut session = Session::new(config, Some(out));
+    format_and_emit_report(&mut session, Input::Text(input));
+
+    let exit_code = if session.has_operational_errors() || session.has_parsing_errors() {
+        1
+    } else {
+        0
+    };
+    Ok(exit_code)
 }
 
 fn format(
     files: Vec<PathBuf>,
     minimal_config_path: Option<String>,
-    options: CliOptions,
-) -> FmtResult<(WriteMode, Summary)> {
+    options: &GetOptsOptions,
+) -> Result<i32, FailureError> {
     options.verify_file_lines(&files);
-    let (config, config_path) = load_config(None, Some(&options))?;
+    let (config, config_path) = load_config(None, Some(options.clone()))?;
 
     if config.verbose() == Verbosity::Verbose {
         if let Some(path) = config_path.as_ref() {
@@ -233,21 +274,21 @@ fn format(
         }
     }
 
-    emit_pre_matter(&config)?;
-    let mut error_summary = Summary::default();
+    let out = &mut stdout();
+    let mut session = Session::new(config, Some(out));
 
     for file in files {
         if !file.exists() {
             eprintln!("Error: file `{}` does not exist", file.to_str().unwrap());
-            error_summary.add_operational_error();
+            session.add_operational_error();
         } else if file.is_dir() {
             eprintln!("Error: `{}` is a directory", file.to_str().unwrap());
-            error_summary.add_operational_error();
+            session.add_operational_error();
         } else {
             // Check the file directory if the config-path could not be read or not provided
-            let local_config = if config_path.is_none() {
+            if config_path.is_none() {
                 let (local_config, config_path) =
-                    load_config(Some(file.parent().unwrap()), Some(&options))?;
+                    load_config(Some(file.parent().unwrap()), Some(options.clone()))?;
                 if local_config.verbose() == Verbosity::Verbose {
                     if let Some(path) = config_path {
                         println!(
@@ -257,31 +298,65 @@ fn format(
                         );
                     }
                 }
-                local_config
-            } else {
-                config.clone()
-            };
 
-            match format_and_emit_report(Input::File(file), &local_config) {
-                Ok(summary) => error_summary.add(summary),
-                Err(_) => {
-                    error_summary.add_operational_error();
-                    break;
-                }
+                session.override_config(local_config, |sess| {
+                    format_and_emit_report(sess, Input::File(file))
+                });
+            } else {
+                format_and_emit_report(&mut session, Input::File(file));
             }
         }
     }
-    emit_post_matter(&config)?;
 
     // If we were given a path via dump-minimal-config, output any options
     // that were used during formatting as TOML.
     if let Some(path) = minimal_config_path {
         let mut file = File::create(path)?;
-        let toml = config.used_options().to_toml().map_err(err_msg)?;
+        let toml = session.config.used_options().to_toml().map_err(err_msg)?;
         file.write_all(toml.as_bytes())?;
     }
 
-    Ok((config.write_mode(), error_summary))
+    let exit_code = if session.has_operational_errors()
+        || session.has_parsing_errors()
+        || ((session.has_diff() || session.has_check_errors()) && options.check)
+    {
+        1
+    } else {
+        0
+    };
+    Ok(exit_code)
+}
+
+fn format_and_emit_report<T: Write>(session: &mut Session<'_, T>, input: Input) {
+    match session.format(input) {
+        Ok(report) => {
+            if report.has_warnings() {
+                eprintln!(
+                    "{}",
+                    FormatReportFormatterBuilder::new(&report)
+                        .enable_colors(should_print_with_colors(session))
+                        .build()
+                );
+            }
+        }
+        Err(msg) => {
+            eprintln!("Error writing files: {}", msg);
+            session.add_operational_error();
+        }
+    }
+}
+
+fn should_print_with_colors<T: Write>(session: &mut Session<'_, T>) -> bool {
+    match term::stderr() {
+        Some(ref t)
+            if session.config.color().use_colored_tty()
+                && t.supports_color()
+                && t.supports_attr(term::Attr::Bold) =>
+        {
+            true
+        }
+        _ => false,
+    }
 }
 
 fn print_usage_to_stdout(opts: &Options, reason: &str) {
@@ -298,6 +373,29 @@ fn print_usage_to_stdout(opts: &Options, reason: &str) {
     println!("{}", opts.usage(&msg));
 }
 
+fn print_help_file_lines() {
+    println!(
+        "If you want to restrict reformatting to specific sets of lines, you can
+use the `--file-lines` option. Its argument is a JSON array of objects
+with `file` and `range` properties, where `file` is a file name, and
+`range` is an array representing a range of lines like `[7,13]`. Ranges
+are 1-based and inclusive of both end points. Specifying an empty array
+will result in no files being formatted. For example,
+
+```
+rustfmt --file-lines '[
+    {{\"file\":\"src/lib.rs\",\"range\":[7,13]}},
+    {{\"file\":\"src/lib.rs\",\"range\":[21,29]}},
+    {{\"file\":\"src/foo.rs\",\"range\":[10,11]}},
+    {{\"file\":\"src/foo.rs\",\"range\":[15,15]}}]'
+```
+
+would format lines `7-13` and `21-29` of `src/lib.rs`, and lines `10-11`,
+and `15` of `src/foo.rs`. No other files would be formatted, even if they
+are included as out of line modules from `src/lib.rs`."
+    );
+}
+
 fn print_version() {
     let version_info = format!(
         "{}-{}",
@@ -308,28 +406,36 @@ fn print_version() {
     println!("rustfmt {}", version_info);
 }
 
-fn determine_operation(matches: &Matches) -> FmtResult<Operation> {
+fn determine_operation(matches: &Matches) -> Result<Operation, OperationError> {
     if matches.opt_present("h") {
-        return Ok(Operation::Help);
-    }
-
-    if matches.opt_present("config-help") {
-        return Ok(Operation::ConfigHelp);
-    }
-
-    if matches.opt_present("dump-default-config") {
-        // NOTE for some reason when configured with HasArg::Maybe + Occur::Optional opt_default
-        // doesn't recognize `--foo bar` as a long flag with an argument but as a long flag with no
-        // argument *plus* a free argument. Thus we check for that case in this branch -- this is
-        // required for backward compatibility.
-        if let Some(path) = matches.free.get(0) {
-            return Ok(Operation::ConfigOutputDefault {
-                path: Some(path.clone()),
-            });
+        let topic = matches.opt_str("h");
+        if topic == None {
+            return Ok(Operation::Help(HelpOp::None));
+        } else if topic == Some("config".to_owned()) {
+            return Ok(Operation::Help(HelpOp::Config));
+        } else if topic == Some("file-lines".to_owned()) {
+            return Ok(Operation::Help(HelpOp::FileLines));
         } else {
-            return Ok(Operation::ConfigOutputDefault {
-                path: matches.opt_str("dump-default-config"),
-            });
+            return Err(OperationError::UnknownHelpTopic(topic.unwrap()));
+        }
+    }
+    let mut free_matches = matches.free.iter();
+
+    let mut minimal_config_path = None;
+    if let Some(kind) = matches.opt_str("print-config") {
+        let path = free_matches.next().cloned();
+        match kind.as_str() {
+            "default" => return Ok(Operation::ConfigOutputDefault { path }),
+            "current" => return Ok(Operation::ConfigOutputCurrent { path }),
+            "minimal" => {
+                minimal_config_path = path;
+                if minimal_config_path.is_none() {
+                    eprintln!("WARNING: PATH required for `--print-config minimal`.");
+                }
+            }
+            _ => {
+                return Err(OperationError::UnknownPrintConfigTopic(kind));
+            }
         }
     }
 
@@ -337,20 +443,7 @@ fn determine_operation(matches: &Matches) -> FmtResult<Operation> {
         return Ok(Operation::Version);
     }
 
-    // If no path is given, we won't output a minimal config.
-    let minimal_config_path = matches.opt_str("dump-minimal-config");
-
-    // if no file argument is supplied, read from stdin
-    if matches.free.is_empty() {
-        let mut buffer = String::new();
-        io::stdin().read_to_string(&mut buffer)?;
-
-        return Ok(Operation::Stdin { input: buffer });
-    }
-
-    let files: Vec<_> = matches
-        .free
-        .iter()
+    let files: Vec<_> = free_matches
         .map(|s| {
             let p = PathBuf::from(s);
             // we will do comparison later, so here tries to canonicalize first
@@ -359,8 +452,200 @@ fn determine_operation(matches: &Matches) -> FmtResult<Operation> {
         })
         .collect();
 
+    // if no file argument is supplied, read from stdin
+    if files.is_empty() {
+        if minimal_config_path.is_some() {
+            return Err(OperationError::MinimalPathWithStdin);
+        }
+        let mut buffer = String::new();
+        io::stdin().read_to_string(&mut buffer)?;
+
+        return Ok(Operation::Stdin { input: buffer });
+    }
+
     Ok(Operation::Format {
         files,
         minimal_config_path,
     })
 }
+
+const STABLE_EMIT_MODES: [EmitMode; 3] = [EmitMode::Files, EmitMode::Stdout, EmitMode::Diff];
+
+/// Parsed command line options.
+#[derive(Clone, Debug, Default)]
+struct GetOptsOptions {
+    skip_children: Option<bool>,
+    quiet: bool,
+    verbose: bool,
+    config_path: Option<PathBuf>,
+    emit_mode: EmitMode,
+    backup: bool,
+    check: bool,
+    edition: Option<Edition>,
+    color: Option<Color>,
+    file_lines: FileLines, // Default is all lines in all files.
+    unstable_features: bool,
+    error_on_unformatted: Option<bool>,
+    print_misformatted_file_names: bool,
+}
+
+impl GetOptsOptions {
+    pub fn from_matches(matches: &Matches) -> Result<GetOptsOptions, FailureError> {
+        let mut options = GetOptsOptions::default();
+        options.verbose = matches.opt_present("verbose");
+        options.quiet = matches.opt_present("quiet");
+        if options.verbose && options.quiet {
+            return Err(format_err!("Can't use both `--verbose` and `--quiet`"));
+        }
+
+        let rust_nightly = is_nightly();
+
+        if rust_nightly {
+            options.unstable_features = matches.opt_present("unstable-features");
+
+            if options.unstable_features {
+                if matches.opt_present("skip-children") {
+                    options.skip_children = Some(true);
+                }
+                if matches.opt_present("error-on-unformatted") {
+                    options.error_on_unformatted = Some(true);
+                }
+                if let Some(ref file_lines) = matches.opt_str("file-lines") {
+                    options.file_lines = file_lines.parse().map_err(err_msg)?;
+                }
+            } else {
+                let mut unstable_options = vec![];
+                if matches.opt_present("skip-children") {
+                    unstable_options.push("`--skip-children`");
+                }
+                if matches.opt_present("error-on-unformatted") {
+                    unstable_options.push("`--error-on-unformatted`");
+                }
+                if matches.opt_present("file-lines") {
+                    unstable_options.push("`--file-lines`");
+                }
+                if !unstable_options.is_empty() {
+                    let s = if unstable_options.len() == 1 { "" } else { "s" };
+                    return Err(format_err!(
+                        "Unstable option{} ({}) used without `--unstable-features`",
+                        s,
+                        unstable_options.join(", "),
+                    ));
+                }
+            }
+        }
+
+        options.config_path = matches.opt_str("config-path").map(PathBuf::from);
+
+        options.check = matches.opt_present("check");
+        if let Some(ref emit_str) = matches.opt_str("emit") {
+            if options.check {
+                return Err(format_err!("Invalid to use `--emit` and `--check`"));
+            }
+
+            options.emit_mode = emit_mode_from_emit_str(emit_str)?;
+        }
+
+        if let Some(ref edition_str) = matches.opt_str("edition") {
+            options.edition = Some(edition_from_edition_str(edition_str)?);
+        }
+
+        if matches.opt_present("backup") {
+            options.backup = true;
+        }
+
+        if matches.opt_present("files-with-diff") {
+            options.print_misformatted_file_names = true;
+        }
+
+        if !rust_nightly {
+            if !STABLE_EMIT_MODES.contains(&options.emit_mode) {
+                return Err(format_err!(
+                    "Invalid value for `--emit` - using an unstable \
+                     value without `--unstable-features`",
+                ));
+            }
+        }
+
+        if let Some(ref color) = matches.opt_str("color") {
+            match Color::from_str(color) {
+                Ok(color) => options.color = Some(color),
+                _ => return Err(format_err!("Invalid color: {}", color)),
+            }
+        }
+
+        Ok(options)
+    }
+
+    fn verify_file_lines(&self, files: &[PathBuf]) {
+        for f in self.file_lines.files() {
+            match *f {
+                FileName::Real(ref f) if files.contains(f) => {}
+                FileName::Real(_) => {
+                    eprintln!("Warning: Extra file listed in file_lines option '{}'", f)
+                }
+                FileName::Stdin => eprintln!("Warning: Not a file '{}'", f),
+            }
+        }
+    }
+}
+
+impl CliOptions for GetOptsOptions {
+    fn apply_to(self, config: &mut Config) {
+        if self.verbose {
+            config.set().verbose(Verbosity::Verbose);
+        } else if self.quiet {
+            config.set().verbose(Verbosity::Quiet);
+        } else {
+            config.set().verbose(Verbosity::Normal);
+        }
+        config.set().file_lines(self.file_lines);
+        config.set().unstable_features(self.unstable_features);
+        if let Some(skip_children) = self.skip_children {
+            config.set().skip_children(skip_children);
+        }
+        if let Some(error_on_unformatted) = self.error_on_unformatted {
+            config.set().error_on_unformatted(error_on_unformatted);
+        }
+        if let Some(edition) = self.edition {
+            config.set().edition(edition);
+        }
+        if self.check {
+            config.set().emit_mode(EmitMode::Diff);
+        } else {
+            config.set().emit_mode(self.emit_mode);
+        }
+        if self.backup {
+            config.set().make_backup(true);
+        }
+        if let Some(color) = self.color {
+            config.set().color(color);
+        }
+        if self.print_misformatted_file_names {
+            config.set().print_misformatted_file_names(true);
+        }
+    }
+
+    fn config_path(&self) -> Option<&Path> {
+        self.config_path.as_ref().map(|p| &**p)
+    }
+}
+
+fn edition_from_edition_str(edition_str: &str) -> Result<Edition, FailureError> {
+    match edition_str {
+        "2015" => Ok(Edition::Edition2015),
+        "2018" => Ok(Edition::Edition2018),
+        _ => Err(format_err!("Invalid value for `--edition`")),
+    }
+}
+
+fn emit_mode_from_emit_str(emit_str: &str) -> Result<EmitMode, FailureError> {
+    match emit_str {
+        "files" => Ok(EmitMode::Files),
+        "stdout" => Ok(EmitMode::Stdout),
+        "coverage" => Ok(EmitMode::Coverage),
+        "checkstyle" => Ok(EmitMode::Checkstyle),
+        "json" => Ok(EmitMode::Json),
+        _ => Err(format_err!("Invalid value for `--emit`")),
+    }
+}