]>
git.proxmox.com Git - rustc.git/blob - src/tools/rustfmt/src/format-diff/main.rs
1 // Inspired by Clang's clang-format-diff:
3 // https://github.com/llvm-mirror/clang/blob/master/tools/clang-format/clang-format-diff.py
11 use serde
::{Deserialize, Serialize}
;
12 use serde_json
as json
;
15 use std
::collections
::HashSet
;
18 use std
::io
::{self, BufRead}
;
23 use structopt
::clap
::AppSettings
;
24 use structopt
::StructOpt
;
26 /// The default pattern of files to format.
28 /// We only want to format rust files by default.
29 const DEFAULT_PATTERN
: &str = r
".*\.rs";
31 #[derive(Error, Debug)]
32 enum FormatDiffError
{
34 IncorrectOptions(#[from] getopts::Fail),
36 IncorrectFilter(#[from] regex::Error),
38 IoError(#[from] io::Error),
41 #[derive(StructOpt, Debug)]
43 name
= "rustfmt-format-diff",
44 setting
= AppSettings
::DisableVersion
,
45 setting
= AppSettings
::NextLineHelp
48 /// Skip the smallest prefix containing NUMBER slashes
52 value_name
= "NUMBER",
57 /// Custom pattern selecting file paths to reformat
61 value_name
= "PATTERN",
62 default_value
= DEFAULT_PATTERN
69 let opts
= Opts
::from_args();
70 if let Err(e
) = run(opts
) {
72 Opts
::clap().print_help().expect("cannot write to stdout");
77 #[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
83 fn run(opts
: Opts
) -> Result
<(), FormatDiffError
> {
84 let (files
, ranges
) = scan_diff(io
::stdin(), opts
.skip_prefix
, &opts
.filter
)?
;
85 run_rustfmt(&files
, &ranges
)
88 fn run_rustfmt(files
: &HashSet
<String
>, ranges
: &[Range
]) -> Result
<(), FormatDiffError
> {
89 if files
.is_empty() || ranges
.is_empty() {
90 debug
!("No files to format found");
94 let ranges_as_json
= json
::to_string(ranges
).unwrap();
96 debug
!("Files: {:?}", files
);
97 debug
!("Ranges: {:?}", ranges
);
99 let rustfmt_var
= env
::var_os("RUSTFMT");
100 let rustfmt
= match &rustfmt_var
{
101 Some(rustfmt
) => rustfmt
,
102 None
=> OsStr
::new("rustfmt"),
104 let exit_status
= process
::Command
::new(rustfmt
)
110 if !exit_status
.success() {
111 return Err(FormatDiffError
::IoError(io
::Error
::new(
112 io
::ErrorKind
::Other
,
113 format
!("rustfmt failed with {}", exit_status
),
119 /// Scans a diff from `from`, and returns the set of files found, and the ranges
125 ) -> Result
<(HashSet
<String
>, Vec
<Range
>), FormatDiffError
>
129 let diff_pattern
= format
!(r
"^\+\+\+\s(?:.*?/){{{}}}(\S*)", skip_prefix
);
130 let diff_pattern
= Regex
::new(&diff_pattern
).unwrap();
132 let lines_pattern
= Regex
::new(r
"^@@.*\+(\d+)(,(\d+))?").unwrap();
134 let file_filter
= Regex
::new(&format
!("^{}$", file_filter
))?
;
136 let mut current_file
= None
;
138 let mut files
= HashSet
::new();
139 let mut ranges
= vec
![];
140 for line
in io
::BufReader
::new(from
).lines() {
141 let line
= line
.unwrap();
143 if let Some(captures
) = diff_pattern
.captures(&line
) {
144 current_file
= Some(captures
.get(1).unwrap().as_str().to_owned());
147 let file
= match current_file
{
152 // FIXME(emilio): We could avoid this most of the time if needed, but
153 // it's not clear it's worth it.
154 if !file_filter
.is_match(file
) {
158 let lines_captures
= match lines_pattern
.captures(&line
) {
159 Some(captures
) => captures
,
163 let start_line
= lines_captures
169 let line_count
= match lines_captures
.get(3) {
170 Some(line_count
) => line_count
.as_str().parse
::<u32>().unwrap(),
178 let end_line
= start_line
+ line_count
- 1;
179 files
.insert(file
.to_owned());
181 file
: file
.to_owned(),
182 range
: [start_line
, end_line
],
190 fn scan_simple_git_diff() {
191 const DIFF
: &str = include_str
!("test/bindgen.diff");
192 let (files
, ranges
) = scan_diff(DIFF
.as_bytes(), 1, r
".*\.rs").expect("scan_diff failed?");
195 files
.contains("src/ir/traversal.rs"),
196 "Should've matched the filter"
200 !files
.contains("tests/headers/anon_enum.hpp"),
201 "Shouldn't have matched the filter"
208 file
: "src/ir/item.rs".to_owned(),
212 file
: "src/ir/item.rs".to_owned(),
216 file
: "src/ir/traversal.rs".to_owned(),
220 file
: "src/ir/traversal.rs".to_owned(),
232 fn default_options() {
233 let empty
: Vec
<String
> = vec
![];
234 let o
= Opts
::from_iter(&empty
);
235 assert_eq
!(DEFAULT_PATTERN
, o
.filter
);
236 assert_eq
!(0, o
.skip_prefix
);
241 let o
= Opts
::from_iter(&["test", "-p", "10", "-f", r
".*\.hs"]);
242 assert_eq
!(r
".*\.hs", o
.filter
);
243 assert_eq
!(10, o
.skip_prefix
);
247 fn unexpected_option() {
250 .get_matches_from_safe(&["test", "unexpected"])
256 fn unexpected_flag() {
259 .get_matches_from_safe(&["test", "--flag"])
265 fn overridden_option() {
268 .get_matches_from_safe(&["test", "-p", "10", "-p", "20"])
274 fn negative_filter() {
277 .get_matches_from_safe(&["test", "-p", "-1"])