]> git.lizzy.rs Git - rust.git/blobdiff - src/bin/rustfmt.rs
Fix a typo
[rust.git] / src / bin / rustfmt.rs
index 2c741934a5eab9af195759a2caea5fc9422ce2d8..75b45156acec6d6e7a5f8aaa937fbbd868b13839 100644 (file)
 
 #![cfg(not(test))]
 
-
-extern crate log;
-extern crate rustfmt;
-extern crate toml;
 extern crate env_logger;
 extern crate getopts;
-
-use rustfmt::{run, Input, Summary};
-use rustfmt::file_lines::FileLines;
-use rustfmt::config::{Config, WriteMode};
+extern crate rustfmt_nightly as rustfmt;
 
 use std::{env, error};
-use std::fs::{self, File};
-use std::io::{self, ErrorKind, Read, Write};
+use std::fs::File;
+use std::io::{self, Read, Write};
 use std::path::{Path, PathBuf};
 use std::str::FromStr;
 
 use getopts::{Matches, Options};
 
-// Include git commit hash and worktree status; contents are like
-//   const COMMIT_HASH: Option<&'static str> = Some("c31a366");
-//   const WORKTREE_CLEAN: Option<bool> = Some(false);
-// with `None` if running git failed, eg if it is not installed.
-include!(concat!(env!("OUT_DIR"), "/git_info.rs"));
+use rustfmt::{run, Input, Summary};
+use rustfmt::file_lines::FileLines;
+use rustfmt::config::{get_toml_path, Color, Config, WriteMode};
 
 type FmtError = Box<error::Error + Send + Sync>;
 type FmtResult<T> = std::result::Result<T, FmtError>;
@@ -44,6 +35,7 @@ enum Operation {
     Format {
         files: Vec<PathBuf>,
         config_path: Option<PathBuf>,
+        minimal_config_path: Option<String>,
     },
     /// Print the help message.
     Help,
@@ -51,6 +43,10 @@ enum Operation {
     Version,
     /// Print detailed configuration help.
     ConfigHelp,
+    /// Output default config to a file, or stdout if None
+    ConfigOutputDefault {
+        path: Option<String>,
+    },
     /// No file specified, read from stdin
     Stdin {
         input: String,
@@ -64,7 +60,9 @@ struct CliOptions {
     skip_children: bool,
     verbose: bool,
     write_mode: Option<WriteMode>,
+    color: Option<Color>,
     file_lines: FileLines, // Default is all lines in all files.
+    unstable_features: bool,
 }
 
 impl CliOptions {
@@ -72,220 +70,253 @@ fn from_matches(matches: &Matches) -> FmtResult<CliOptions> {
         let mut options = CliOptions::default();
         options.skip_children = matches.opt_present("skip-children");
         options.verbose = matches.opt_present("verbose");
+        let unstable_features = matches.opt_present("unstable-features");
+        let rust_nightly = option_env!("CFG_RELEASE_CHANNEL")
+            .map(|c| c == "nightly")
+            .unwrap_or(false);
+        if unstable_features && !rust_nightly {
+            return Err(FmtError::from(
+                "Unstable features are only available on Nightly channel",
+            ));
+        } else {
+            options.unstable_features = unstable_features;
+        }
 
         if let Some(ref write_mode) = matches.opt_str("write-mode") {
             if let Ok(write_mode) = WriteMode::from_str(write_mode) {
                 options.write_mode = Some(write_mode);
             } else {
-                return Err(FmtError::from(format!("Invalid write-mode: {}", write_mode)));
+                return Err(FmtError::from(format!(
+                    "Invalid write-mode: {}",
+                    write_mode
+                )));
+            }
+        }
+
+        if let Some(ref color) = matches.opt_str("color") {
+            match Color::from_str(color) {
+                Ok(color) => options.color = Some(color),
+                _ => return Err(FmtError::from(format!("Invalid color: {}", color))),
             }
         }
 
         if let Some(ref file_lines) = matches.opt_str("file-lines") {
-            options.file_lines = try!(file_lines.parse());
+            options.file_lines = file_lines.parse()?;
         }
 
         Ok(options)
     }
 
     fn apply_to(self, config: &mut Config) {
-        config.skip_children = self.skip_children;
-        config.verbose = self.verbose;
-        config.file_lines = self.file_lines;
+        config.set().skip_children(self.skip_children);
+        config.set().verbose(self.verbose);
+        config.set().file_lines(self.file_lines);
+        config.set().unstable_features(self.unstable_features);
         if let Some(write_mode) = self.write_mode {
-            config.write_mode = write_mode;
-        }
-    }
-}
-
-const CONFIG_FILE_NAMES: [&'static str; 2] = [".rustfmt.toml", "rustfmt.toml"];
-
-/// Try to find a project file in the given directory and its parents. Returns the path of a the
-/// nearest project file if one exists, or `None` if no project file was found.
-fn lookup_project_file(dir: &Path) -> FmtResult<Option<PathBuf>> {
-    let mut current = if dir.is_relative() {
-        try!(env::current_dir()).join(dir)
-    } else {
-        dir.to_path_buf()
-    };
-
-    current = try!(fs::canonicalize(current));
-
-    loop {
-        for config_file_name in &CONFIG_FILE_NAMES {
-            let config_file = current.join(config_file_name);
-            match fs::metadata(&config_file) {
-                // Only return if it's a file to handle the unlikely situation of a directory named
-                // `rustfmt.toml`.
-                Ok(ref md) if md.is_file() => return Ok(Some(config_file)),
-                // Return the error if it's something other than `NotFound`; otherwise we didn't
-                // find the project file yet, and continue searching.
-                Err(e) => {
-                    if e.kind() != ErrorKind::NotFound {
-                        return Err(FmtError::from(e));
-                    }
-                }
-                _ => {}
-            }
+            config.set().write_mode(write_mode);
         }
-
-        // If the current directory has no parent, we're done searching.
-        if !current.pop() {
-            return Ok(None);
+        if let Some(color) = self.color {
+            config.set().color(color);
         }
     }
 }
 
-fn open_config_file(file_path: &Path) -> FmtResult<(Config, Option<PathBuf>)> {
-    let mut file = try!(File::open(&file_path));
-    let mut toml = String::new();
-    try!(file.read_to_string(&mut toml));
-    match Config::from_toml(&toml) {
-        Ok(cfg) => Ok((cfg, Some(file_path.to_path_buf()))),
-        Err(err) => Err(FmtError::from(err)),
-    }
-}
-
-/// Resolve the config for input in `dir`.
-///
-/// Returns the `Config` to use, and the path of the project file if there was
-/// one.
-fn resolve_config(dir: &Path) -> FmtResult<(Config, Option<PathBuf>)> {
-    let path = try!(lookup_project_file(dir));
-    if path.is_none() {
-        return Ok((Config::default(), None));
-    }
-    open_config_file(&path.unwrap())
-}
-
 /// read the given config file path recursively if present else read the project file path
-fn match_cli_path_or_file(config_path: Option<PathBuf>,
-                          input_file: &Path)
-                          -> FmtResult<(Config, Option<PathBuf>)> {
-
+fn match_cli_path_or_file(
+    config_path: Option<PathBuf>,
+    input_file: &Path,
+) -> FmtResult<(Config, Option<PathBuf>)> {
     if let Some(config_file) = config_path {
-        let (toml, path) = try!(open_config_file(config_file.as_ref()));
-        if path.is_some() {
-            return Ok((toml, path));
-        }
+        let toml = Config::from_toml_path(config_file.as_ref())?;
+        return Ok((toml, Some(config_file)));
     }
-    resolve_config(input_file)
+    Config::from_resolved_toml_path(input_file).map_err(FmtError::from)
 }
 
 fn make_opts() -> Options {
     let mut opts = Options::new();
-    opts.optflag("h", "help", "show this message");
-    opts.optflag("V", "version", "show version information");
-    opts.optflag("v", "verbose", "print verbose output");
-    opts.optopt("",
-                "write-mode",
-                "mode to write in (not usable when piping from stdin)",
-                "[replace|overwrite|display|diff|coverage|checkstyle]");
-    opts.optflag("", "skip-children", "don't reformat child modules");
-
-    opts.optflag("",
-                 "config-help",
-                 "show details of rustfmt configuration options");
-    opts.optopt("",
-                "config-path",
-                "Recursively searches the given path for the rustfmt.toml config file. If not \
-                 found reverts to the input file path",
-                "[Path for the configuration file]");
-    opts.optopt("",
-                "file-lines",
-                "Format specified line ranges. See README for more detail on the JSON format.",
-                "JSON");
+
+    opts.optflag("h", "help", "Show this message");
+    opts.optflag("V", "version", "Show version information");
+    opts.optflag("v", "verbose", "Print verbose output");
+    opts.optflag("", "skip-children", "Don't reformat child modules");
+    opts.optflag(
+        "",
+        "unstable-features",
+        "Enables unstable features. Only available on nightly channel",
+    );
+    opts.optflag(
+        "",
+        "config-help",
+        "Show details of rustfmt configuration options",
+    );
+    opts.optflag(
+        "",
+        "error-on-unformatted",
+        "Error if unable to get comments or string literals within max_width, \
+         or they are left with trailing whitespaces",
+    );
+
+    opts.optopt(
+        "",
+        "write-mode",
+        "How to write output (not usable when piping from stdin)",
+        "[replace|overwrite|display|plain|diff|coverage|checkstyle]",
+    );
+    opts.optopt(
+        "",
+        "color",
+        "Use colored output (if supported)",
+        "[always|never|auto]",
+    );
+    opts.optopt(
+        "",
+        "dump-default-config",
+        "Dumps default configuration to PATH. PATH defaults to stdout, if omitted.",
+        "PATH",
+    );
+    opts.optopt(
+        "",
+        "dump-minimal-config",
+        "Dumps configuration options that were checked during formatting to a file.",
+        "PATH",
+    );
+    opts.optopt(
+        "",
+        "config-path",
+        "Recursively searches the given path for the rustfmt.toml config file. If not \
+         found reverts to the input file path",
+        "[Path for the configuration file]",
+    );
+    opts.optopt(
+        "",
+        "file-lines",
+        "Format specified line ranges. See README for more detail on the JSON format.",
+        "JSON",
+    );
 
     opts
 }
 
 fn execute(opts: &Options) -> FmtResult<Summary> {
-    let matches = try!(opts.parse(env::args().skip(1)));
+    let matches = opts.parse(env::args().skip(1))?;
 
-    match try!(determine_operation(&matches)) {
+    match determine_operation(&matches)? {
         Operation::Help => {
-            print_usage(opts, "");
+            print_usage_to_stdout(opts, "");
             Summary::print_exit_codes();
-            Ok(Summary::new())
+            Ok(Summary::default())
         }
         Operation::Version => {
             print_version();
-            Ok(Summary::new())
+            Ok(Summary::default())
         }
         Operation::ConfigHelp => {
             Config::print_docs();
-            Ok(Summary::new())
+            Ok(Summary::default())
+        }
+        Operation::ConfigOutputDefault { path } => {
+            let toml = Config::default().all_options().to_toml()?;
+            if let Some(path) = path {
+                let mut file = File::create(path)?;
+                file.write_all(toml.as_bytes())?;
+            } else {
+                io::stdout().write_all(toml.as_bytes())?;
+            }
+            Ok(Summary::default())
         }
         Operation::Stdin { input, config_path } => {
             // try to read config from local directory
-            let (mut config, _) = match_cli_path_or_file(config_path,
-                                                         &env::current_dir().unwrap())?;
+            let (mut config, _) =
+                match_cli_path_or_file(config_path, &env::current_dir().unwrap())?;
 
             // write_mode is always Plain for Stdin.
-            config.write_mode = WriteMode::Plain;
+            config.set().write_mode(WriteMode::Plain);
 
             // parse file_lines
             if let Some(ref file_lines) = matches.opt_str("file-lines") {
-                config.file_lines = try!(file_lines.parse());
-                for f in config.file_lines.files() {
+                config.set().file_lines(file_lines.parse()?);
+                for f in config.file_lines().files() {
                     if f != "stdin" {
-                        println!("Warning: Extra file listed in file_lines option '{}'", f);
+                        eprintln!("Warning: Extra file listed in file_lines option '{}'", f);
                     }
                 }
             }
 
-            Ok(run(Input::Text(input), &config))
+            let mut error_summary = Summary::default();
+            if config.version_meets_requirement(&mut error_summary) {
+                error_summary.add(run(Input::Text(input), &config));
+            }
+
+            Ok(error_summary)
         }
-        Operation::Format { files, config_path } => {
-            let options = try!(CliOptions::from_matches(&matches));
+        Operation::Format {
+            files,
+            config_path,
+            minimal_config_path,
+        } => {
+            let options = CliOptions::from_matches(&matches)?;
 
             for f in options.file_lines.files() {
                 if !files.contains(&PathBuf::from(f)) {
-                    println!("Warning: Extra file listed in file_lines option '{}'", f);
+                    eprintln!("Warning: Extra file listed in file_lines option '{}'", f);
                 }
             }
 
             let mut config = Config::default();
-            let mut path = None;
             // Load the config path file if provided
-            if let Some(config_file) = config_path {
-                let (cfg_tmp, path_tmp) = open_config_file(config_file.as_ref())?;
-                config = cfg_tmp;
-                path = path_tmp;
+            if let Some(config_file) = config_path.as_ref() {
+                config = Config::from_toml_path(config_file.as_ref())?;
             };
 
             if options.verbose {
-                if let Some(path) = path.as_ref() {
+                if let Some(path) = config_path.as_ref() {
                     println!("Using rustfmt config file {}", path.display());
                 }
             }
 
-            let mut error_summary = Summary::new();
+            let mut error_summary = Summary::default();
             for file in files {
                 if !file.exists() {
-                    println!("Error: file `{}` does not exist", file.to_str().unwrap());
+                    eprintln!("Error: file `{}` does not exist", file.to_str().unwrap());
                     error_summary.add_operational_error();
                 } else if file.is_dir() {
-                    println!("Error: `{}` is a directory", file.to_str().unwrap());
+                    eprintln!("Error: `{}` is a directory", file.to_str().unwrap());
                     error_summary.add_operational_error();
                 } else {
                     // Check the file directory if the config-path could not be read or not provided
-                    if path.is_none() {
-                        let (config_tmp, path_tmp) = resolve_config(file.parent().unwrap())?;
+                    if config_path.is_none() {
+                        let (config_tmp, path_tmp) =
+                            Config::from_resolved_toml_path(file.parent().unwrap())?;
                         if options.verbose {
                             if let Some(path) = path_tmp.as_ref() {
-                                println!("Using rustfmt config file {} for {}",
-                                         path.display(),
-                                         file.display());
+                                println!(
+                                    "Using rustfmt config file {} for {}",
+                                    path.display(),
+                                    file.display()
+                                );
                             }
                         }
                         config = config_tmp;
                     }
 
+                    if !config.version_meets_requirement(&mut error_summary) {
+                        break;
+                    }
+
                     options.clone().apply_to(&mut config);
                     error_summary.add(run(Input::File(file), &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()?;
+                file.write_all(toml.as_bytes())?;
+            }
+
             Ok(error_summary)
         }
     }
@@ -313,7 +344,7 @@ fn main() {
             }
         }
         Err(e) => {
-            print_usage(&opts, &e.to_string());
+            print_usage_to_stderr(&opts, &e.to_string());
             1
         }
     };
@@ -327,21 +358,31 @@ fn main() {
     std::process::exit(exit_code);
 }
 
-fn print_usage(opts: &Options, reason: &str) {
-    let reason = format!("{}\n\nusage: {} [options] <file>...",
-                         reason,
-                         env::args_os().next().unwrap().to_string_lossy());
-    println!("{}", opts.usage(&reason));
+macro_rules! print_usage {
+    ($print:ident, $opts:ident, $reason:expr) => ({
+        let msg = format!(
+            "{}\n\nusage: {} [options] <file>...",
+            $reason,
+            env::args_os().next().unwrap().to_string_lossy()
+        );
+        $print!("{}", $opts.usage(&msg));
+    })
+}
+
+fn print_usage_to_stdout(opts: &Options, reason: &str) {
+    print_usage!(println, opts, reason);
+}
+
+fn print_usage_to_stderr(opts: &Options, reason: &str) {
+    print_usage!(eprintln, opts, reason);
 }
 
 fn print_version() {
-    println!("{} ({}{})",
-             option_env!("CARGO_PKG_VERSION").unwrap_or("unknown"),
-             COMMIT_HASH.unwrap_or("git commit unavailable"),
-             match WORKTREE_CLEAN {
-                 Some(false) => " worktree dirty",
-                 _ => "",
-             });
+    println!(
+        "{}-nightly{}",
+        env!("CARGO_PKG_VERSION"),
+        include_str!(concat!(env!("OUT_DIR"), "/commit-info.txt"))
+    )
 }
 
 fn determine_operation(matches: &Matches) -> FmtResult<Operation> {
@@ -353,13 +394,31 @@ fn determine_operation(matches: &Matches) -> FmtResult<Operation> {
         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()),
+            });
+        } else {
+            return Ok(Operation::ConfigOutputDefault {
+                path: matches.opt_str("dump-default-config"),
+            });
+        }
+    }
+
     if matches.opt_present("version") {
         return Ok(Operation::Version);
     }
 
     let config_path_not_found = |path: &str| -> FmtResult<Operation> {
-        Err(FmtError::from(format!("Error: unable to find a config file for the given path: `{}`",
-                                   path)))
+        Err(FmtError::from(format!(
+            "Error: unable to find a config file for the given path: `{}`",
+            path
+        )))
     };
 
     // Read the config_path and convert to parent dir if a file is provided.
@@ -367,46 +426,44 @@ fn determine_operation(matches: &Matches) -> FmtResult<Operation> {
     let config_path: Option<PathBuf> = match matches.opt_str("config-path").map(PathBuf::from) {
         Some(ref path) if !path.exists() => return config_path_not_found(path.to_str().unwrap()),
         Some(ref path) if path.is_dir() => {
-            let mut config_file_path = None;
-            for config_file_name in &CONFIG_FILE_NAMES {
-                let temp_path = path.join(config_file_name);
-                if temp_path.is_file() {
-                    config_file_path = Some(temp_path);
-                }
-            }
+            let config_file_path = get_toml_path(path)?;
             if config_file_path.is_some() {
                 config_file_path
             } else {
                 return config_path_not_found(path.to_str().unwrap());
             }
         }
-        path @ _ => path,
+        path => path,
     };
 
+    // 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();
-        try!(io::stdin().read_to_string(&mut buffer));
+        io::stdin().read_to_string(&mut buffer)?;
 
         return Ok(Operation::Stdin {
-                      input: buffer,
-                      config_path: config_path,
-                  });
+            input: buffer,
+            config_path: config_path,
+        });
     }
 
     let files: Vec<_> = matches
         .free
         .iter()
         .map(|s| {
-                 let p = PathBuf::from(s);
-                 // we will do comparison later, so here tries to canonicalize first
-                 // to get the expected behavior.
-                 p.canonicalize().unwrap_or(p)
-             })
+            let p = PathBuf::from(s);
+            // we will do comparison later, so here tries to canonicalize first
+            // to get the expected behavior.
+            p.canonicalize().unwrap_or(p)
+        })
         .collect();
 
     Ok(Operation::Format {
-           files: files,
-           config_path: config_path,
-       })
+        files: files,
+        config_path: config_path,
+        minimal_config_path: minimal_config_path,
+    })
 }