From fcc62cbbe0b7b5f3ba0b5e5f206d84b0ebb6c5b7 Mon Sep 17 00:00:00 2001 From: Johann Date: Mon, 2 Nov 2015 20:45:45 +0100 Subject: [PATCH 1/2] Stdin support Adds support for receiving input from stdin in case no file was specified. This is useful for editor/IDE integrations and other tooling. To achieve clean output a new write-mode option called plain was added, this option is mandatory when using stdin. --- src/bin/rustfmt.rs | 48 ++++++++++++++++++++++++++++++++++++++++------ src/filemap.rs | 15 ++++++++++----- src/lib.rs | 42 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+), 11 deletions(-) diff --git a/src/bin/rustfmt.rs b/src/bin/rustfmt.rs index 517bda69a3a..f1dda3b1a79 100644 --- a/src/bin/rustfmt.rs +++ b/src/bin/rustfmt.rs @@ -17,7 +17,7 @@ extern crate toml; extern crate env_logger; extern crate getopts; -use rustfmt::{WriteMode, run}; +use rustfmt::{WriteMode, run, run_from_stdin}; use rustfmt::config::Config; use std::env; @@ -35,6 +35,8 @@ enum Operation { Help, /// Invalid program input, including reason. InvalidInput(String), + /// No file specified, read from stdin + Stdin(String, WriteMode), } /// Try to find a project file in the input file directory and its parents. @@ -76,7 +78,7 @@ fn execute() -> i32 { opts.optopt("", "write-mode", "mode to write in", - "[replace|overwrite|display|diff|coverage]"); + "[replace|overwrite|display|plain|diff|coverage]"); let operation = determine_operation(&opts, env::args().skip(1)); @@ -89,6 +91,18 @@ fn execute() -> i32 { print_usage(&opts, ""); 0 } + Operation::Stdin(input, write_mode) => { + // try to read config from local directory + let config = match lookup_and_read_project_file(&Path::new(".")) { + Ok((path, toml)) => { + Config::from_toml(&toml) + } + Err(_) => Default::default(), + }; + + run_from_stdin(input, write_mode, &config); + 0 + } Operation::Format(file, write_mode) => { let config = match lookup_and_read_project_file(&file) { Ok((path, toml)) => { @@ -138,6 +152,32 @@ fn determine_operation(opts: &Options, args: I) -> Operation return Operation::Help; } + // if no file argument is supplied, read from stdin + if matches.free.len() != 1 { + + // make sure the write mode is plain or not set + // (the other options would require a file) + match matches.opt_str("write-mode") { + Some(mode) => { + match mode.parse() { + Ok(WriteMode::Plain) => (), + _ => return Operation::InvalidInput("Using stdin requires write-mode to be \ + plain" + .into()), + } + } + _ => (), + } + + let mut buffer = String::new(); + match io::stdin().read_to_string(&mut buffer) { + Ok(..) => (), + Err(e) => return Operation::InvalidInput(e.to_string()), + } + + return Operation::Stdin(buffer, WriteMode::Plain); + } + let write_mode = match matches.opt_str("write-mode") { Some(mode) => { match mode.parse() { @@ -148,9 +188,5 @@ fn determine_operation(opts: &Options, args: I) -> Operation None => WriteMode::Replace, }; - if matches.free.len() != 1 { - return Operation::InvalidInput("Please provide one file to format".into()); - } - Operation::Format(PathBuf::from(&matches.free[0]), write_mode) } diff --git a/src/filemap.rs b/src/filemap.rs index 72174679386..a11b89bc54b 100644 --- a/src/filemap.rs +++ b/src/filemap.rs @@ -46,11 +46,11 @@ pub fn write_all_files(file_map: &FileMap, Ok(result) } -fn write_file(text: &StringBuffer, - filename: &str, - mode: WriteMode, - config: &Config) - -> Result, io::Error> { +pub fn write_file(text: &StringBuffer, + filename: &str, + mode: WriteMode, + config: &Config) + -> Result, io::Error> { // prints all newlines either as `\n` or as `\r\n` fn write_system_newlines(mut writer: T, @@ -100,6 +100,11 @@ fn write_file(text: &StringBuffer, let file = try!(File::create(&filename)); try!(write_system_newlines(file, text, config)); } + WriteMode::Plain => { + let stdout = stdout(); + let stdout_lock = stdout.lock(); + try!(write_system_newlines(stdout_lock, text, config)); + } WriteMode::Display | WriteMode::Coverage => { println!("{}:\n", filename); let stdout = stdout(); diff --git a/src/lib.rs b/src/lib.rs index c7c2178e098..4c7b3cbd249 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -193,6 +193,8 @@ pub enum WriteMode { Return, // Display how much of the input file was processed Coverage, + // Unfancy stdout + Plain, } impl FromStr for WriteMode { @@ -205,6 +207,7 @@ impl FromStr for WriteMode { "overwrite" => Ok(WriteMode::Overwrite), "diff" => Ok(WriteMode::Diff), "coverage" => Ok(WriteMode::Coverage), + "plain" => Ok(WriteMode::Plain), _ => Err(()), } } @@ -386,6 +389,33 @@ pub fn fmt_lines(file_map: &mut FileMap, config: &Config) -> FormatReport { report } +pub fn format_string(input: String, config: &Config, mode: WriteMode) -> FileMap { + let path = "stdin"; + let mut parse_session = ParseSess::new(); + let krate = parse::parse_crate_from_source_str(path.to_owned(), + input, + Vec::new(), + &parse_session); + + // Suppress error output after parsing. + let emitter = Box::new(EmitterWriter::new(Box::new(Vec::new()), None)); + parse_session.span_diagnostic.handler = Handler::with_emitter(false, emitter); + + // FIXME: we still use a FileMap even though we only have + // one file, because fmt_lines requires a FileMap + let mut file_map = FileMap::new(); + + // do the actual formatting + let mut visitor = FmtVisitor::from_codemap(&parse_session, config, Some(mode)); + visitor.format_separate_mod(&krate.module, path); + + // append final newline + visitor.buffer.push_str("\n"); + file_map.insert(path.to_owned(), visitor.buffer); + + return file_map; +} + pub fn format(file: &Path, config: &Config, mode: WriteMode) -> FileMap { let mut parse_session = ParseSess::new(); let krate = parse::parse_crate_from_file(file, Vec::new(), &parse_session); @@ -418,3 +448,15 @@ pub fn run(file: &Path, write_mode: WriteMode, config: &Config) { println!("Error writing files: {}", msg); } } + +// Similar to run, but takes an input String instead of a file to format +pub fn run_from_stdin(input: String, mode: WriteMode, config: &Config) { + let mut result = format_string(input, config, mode); + fmt_lines(&mut result, config); + + let write_result = filemap::write_file(&result["stdin"], "stdin", mode, config); + + if let Err(msg) = write_result { + panic!("Error writing to stdout: {}", msg); + } +} From 154e20a04f7b3261c2d94e18f9abd39a056f6bef Mon Sep 17 00:00:00 2001 From: Johann Date: Tue, 3 Nov 2015 09:16:33 +0100 Subject: [PATCH 2/2] Address review concerns --- src/bin/rustfmt.rs | 21 ++++----------------- src/filemap.rs | 8 ++++---- 2 files changed, 8 insertions(+), 21 deletions(-) diff --git a/src/bin/rustfmt.rs b/src/bin/rustfmt.rs index f1dda3b1a79..b8eacc2bcd0 100644 --- a/src/bin/rustfmt.rs +++ b/src/bin/rustfmt.rs @@ -77,8 +77,8 @@ fn execute() -> i32 { opts.optflag("h", "help", "show this message"); opts.optopt("", "write-mode", - "mode to write in", - "[replace|overwrite|display|plain|diff|coverage]"); + "mode to write in (not usable when piping from stdin)", + "[replace|overwrite|display|diff|coverage]"); let operation = determine_operation(&opts, env::args().skip(1)); @@ -153,21 +153,7 @@ fn determine_operation(opts: &Options, args: I) -> Operation } // if no file argument is supplied, read from stdin - if matches.free.len() != 1 { - - // make sure the write mode is plain or not set - // (the other options would require a file) - match matches.opt_str("write-mode") { - Some(mode) => { - match mode.parse() { - Ok(WriteMode::Plain) => (), - _ => return Operation::InvalidInput("Using stdin requires write-mode to be \ - plain" - .into()), - } - } - _ => (), - } + if matches.free.len() == 0 { let mut buffer = String::new(); match io::stdin().read_to_string(&mut buffer) { @@ -175,6 +161,7 @@ fn determine_operation(opts: &Options, args: I) -> Operation Err(e) => return Operation::InvalidInput(e.to_string()), } + // WriteMode is always plain for Stdin return Operation::Stdin(buffer, WriteMode::Plain); } diff --git a/src/filemap.rs b/src/filemap.rs index a11b89bc54b..3f1a867f385 100644 --- a/src/filemap.rs +++ b/src/filemap.rs @@ -102,14 +102,14 @@ pub fn write_file(text: &StringBuffer, } WriteMode::Plain => { let stdout = stdout(); - let stdout_lock = stdout.lock(); - try!(write_system_newlines(stdout_lock, text, config)); + let stdout = stdout.lock(); + try!(write_system_newlines(stdout, text, config)); } WriteMode::Display | WriteMode::Coverage => { println!("{}:\n", filename); let stdout = stdout(); - let stdout_lock = stdout.lock(); - try!(write_system_newlines(stdout_lock, text, config)); + let stdout = stdout.lock(); + try!(write_system_newlines(stdout, text, config)); } WriteMode::Diff => { println!("Diff of {}:\n", filename);