]> git.lizzy.rs Git - rust.git/blob - src/config.rs
Add explicit lifetime
[rust.git] / src / config.rs
1 // Copyright 2015 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10
11 extern crate toml;
12
13 use std::{env, fs};
14 use std::cell::Cell;
15 use std::fs::File;
16 use std::io::{Error, ErrorKind, Read};
17 use std::path::{Path, PathBuf};
18
19 use file_lines::FileLines;
20 use lists::{ListTactic, SeparatorPlace, SeparatorTactic};
21
22 macro_rules! configuration_option_enum{
23     ($e:ident: $( $x:ident ),+ $(,)*) => {
24         #[derive(Copy, Clone, Eq, PartialEq, Debug)]
25         pub enum $e {
26             $( $x ),+
27         }
28
29         impl_enum_serialize_and_deserialize!($e, $( $x ),+);
30     }
31 }
32
33 configuration_option_enum! { Style:
34     Rfc, // Follow the style RFCs style.
35     Legacy, // Follow the traditional Rustfmt style.
36 }
37
38 configuration_option_enum! { NewlineStyle:
39     Windows, // \r\n
40     Unix, // \n
41     Native, // \r\n in Windows, \n on other platforms
42 }
43
44 configuration_option_enum! { BraceStyle:
45     AlwaysNextLine,
46     PreferSameLine,
47     // Prefer same line except where there is a where clause, in which case force
48     // the brace to the next line.
49     SameLineWhere,
50 }
51
52 configuration_option_enum! { ControlBraceStyle:
53     // K&R style, Rust community default
54     AlwaysSameLine,
55     // Stroustrup style
56     ClosingNextLine,
57     // Allman style
58     AlwaysNextLine,
59 }
60
61 // How to indent a function's return type.
62 configuration_option_enum! { ReturnIndent:
63     // Aligned with the arguments
64     WithArgs,
65     // Aligned with the where clause
66     WithWhereClause,
67 }
68
69 configuration_option_enum! { IndentStyle:
70     // First line on the same line as the opening brace, all lines aligned with
71     // the first line.
72     Visual,
73     // First line is on a new line and all lines align with block indent.
74     Block,
75 }
76
77 configuration_option_enum! { Density:
78     // Fit as much on one line as possible.
79     Compressed,
80     // Use more lines.
81     Tall,
82     // Try to compress if the body is empty.
83     CompressedIfEmpty,
84     // Place every item on a separate line.
85     Vertical,
86 }
87
88 configuration_option_enum! { TypeDensity:
89     // No spaces around "=" and "+"
90     Compressed,
91     // Spaces around " = " and " + "
92     Wide,
93 }
94
95
96 impl Density {
97     pub fn to_list_tactic(self) -> ListTactic {
98         match self {
99             Density::Compressed => ListTactic::Mixed,
100             Density::Tall | Density::CompressedIfEmpty => ListTactic::HorizontalVertical,
101             Density::Vertical => ListTactic::Vertical,
102         }
103     }
104 }
105
106 configuration_option_enum! { LicensePolicy:
107     // Do not place license text at top of files
108     NoLicense,
109     // Use the text in "license" field as the license
110     TextLicense,
111     // Use a text file as the license text
112     FileLicense,
113 }
114
115 configuration_option_enum! { MultilineStyle:
116     // Use horizontal layout if it fits in one line, fall back to vertical
117     PreferSingle,
118     // Use vertical layout
119     ForceMulti,
120 }
121
122 impl MultilineStyle {
123     pub fn to_list_tactic(self) -> ListTactic {
124         match self {
125             MultilineStyle::PreferSingle => ListTactic::HorizontalVertical,
126             MultilineStyle::ForceMulti => ListTactic::Vertical,
127         }
128     }
129 }
130
131 configuration_option_enum! { ReportTactic:
132     Always,
133     Unnumbered,
134     Never,
135 }
136
137 configuration_option_enum! { WriteMode:
138     // Backs the original file up and overwrites the original.
139     Replace,
140     // Overwrites original file without backup.
141     Overwrite,
142     // Writes the output to stdout.
143     Display,
144     // Writes the diff to stdout.
145     Diff,
146     // Displays how much of the input file was processed
147     Coverage,
148     // Unfancy stdout
149     Plain,
150     // Outputs a checkstyle XML file.
151     Checkstyle,
152 }
153
154 /// Trait for types that can be used in `Config`.
155 pub trait ConfigType: Sized {
156     /// Returns hint text for use in `Config::print_docs()`. For enum types, this is a
157     /// pipe-separated list of variants; for other types it returns "<type>".
158     fn doc_hint() -> String;
159 }
160
161 impl ConfigType for bool {
162     fn doc_hint() -> String {
163         String::from("<boolean>")
164     }
165 }
166
167 impl ConfigType for usize {
168     fn doc_hint() -> String {
169         String::from("<unsigned integer>")
170     }
171 }
172
173 impl ConfigType for isize {
174     fn doc_hint() -> String {
175         String::from("<signed integer>")
176     }
177 }
178
179 impl ConfigType for String {
180     fn doc_hint() -> String {
181         String::from("<string>")
182     }
183 }
184
185 impl ConfigType for FileLines {
186     fn doc_hint() -> String {
187         String::from("<json>")
188     }
189 }
190
191 pub struct ConfigHelpItem {
192     option_name: &'static str,
193     doc_string: &'static str,
194     variant_names: String,
195     default: &'static str,
196 }
197
198 impl ConfigHelpItem {
199     pub fn option_name(&self) -> &'static str {
200         self.option_name
201     }
202
203     pub fn doc_string(&self) -> &'static str {
204         self.doc_string
205     }
206
207     pub fn variant_names(&self) -> &String {
208         &self.variant_names
209     }
210
211     pub fn default(&self) -> &'static str {
212         self.default
213     }
214 }
215
216 macro_rules! create_config {
217     ($($i:ident: $ty:ty, $def:expr, $( $dstring:expr ),+ );+ $(;)*) => (
218         #[derive(Clone)]
219         pub struct Config {
220             // For each config item, we store a bool indicating whether it has
221             // been accessed and the value, and a bool whether the option was
222             // manually initialised, or taken from the default,
223             $($i: (Cell<bool>, bool, $ty)),+
224         }
225
226         // Just like the Config struct but with each property wrapped
227         // as Option<T>. This is used to parse a rustfmt.toml that doesn't
228         // specity all properties of `Config`.
229         // We first parse into `PartialConfig`, then create a default `Config`
230         // and overwrite the properties with corresponding values from `PartialConfig`.
231         #[derive(Deserialize, Serialize, Clone)]
232         pub struct PartialConfig {
233             $(pub $i: Option<$ty>),+
234         }
235
236         impl PartialConfig {
237             pub fn to_toml(&self) -> Result<String, String> {
238                 // file_lines can't be specified in TOML
239                 let mut cloned = self.clone();
240                 cloned.file_lines = None;
241
242                 toml::to_string(&cloned)
243                     .map_err(|e| format!("Could not output config: {}", e.to_string()))
244             }
245         }
246
247         // Macro hygiene won't allow us to make `set_$i()` methods on Config
248         // for each item, so this struct is used to give the API to set values:
249         // `config.get().option(false)`. It's pretty ugly. Consider replacing
250         // with `config.set_option(false)` if we ever get a stable/usable
251         // `concat_idents!()`.
252         pub struct ConfigSetter<'a>(&'a mut Config);
253
254         impl<'a> ConfigSetter<'a> {
255             $(
256             pub fn $i(&mut self, value: $ty) {
257                 (self.0).$i.2 = value;
258             }
259             )+
260         }
261
262         // Query each option, returns true if the user set the option, false if
263         // a default was used.
264         pub struct ConfigWasSet<'a>(&'a Config);
265
266         impl<'a> ConfigWasSet<'a> {
267             $(
268             pub fn $i(&self) -> bool {
269                 (self.0).$i.1
270             }
271             )+
272         }
273
274         impl Config {
275
276             $(
277             pub fn $i(&self) -> $ty {
278                 self.$i.0.set(true);
279                 self.$i.2.clone()
280             }
281             )+
282
283             pub fn set<'a>(&'a mut self) -> ConfigSetter<'a> {
284                 ConfigSetter(self)
285             }
286
287             pub fn was_set<'a>(&'a self) -> ConfigWasSet<'a> {
288                 ConfigWasSet(self)
289             }
290
291             fn fill_from_parsed_config(mut self, parsed: PartialConfig) -> Config {
292             $(
293                 if let Some(val) = parsed.$i {
294                     self.$i.1 = true;
295                     self.$i.2 = val;
296                 }
297             )+
298                 self
299             }
300
301             pub fn from_toml(toml: &str) -> Result<Config, String> {
302                 let parsed: toml::Value =
303                     toml.parse().map_err(|e| format!("Could not parse TOML: {}", e))?;
304                 let mut err: String = String::new();
305                 {
306                     let table = parsed
307                         .as_table()
308                         .ok_or(String::from("Parsed config was not table"))?;
309                     for key in table.keys() {
310                         match &**key {
311                             $(
312                                 stringify!($i) => (),
313                             )+
314                                 _ => {
315                                     let msg =
316                                         &format!("Warning: Unknown configuration option `{}`\n",
317                                                  key);
318                                     err.push_str(msg)
319                                 }
320                         }
321                     }
322                 }
323                 match parsed.try_into() {
324                     Ok(parsed_config) =>
325                         Ok(Config::default().fill_from_parsed_config(parsed_config)),
326                     Err(e) => {
327                         err.push_str("Error: Decoding config file failed:\n");
328                         err.push_str(format!("{}\n", e).as_str());
329                         err.push_str("Please check your config file.\n");
330                         Err(err)
331                     }
332                 }
333             }
334
335             pub fn used_options(&self) -> PartialConfig {
336                 PartialConfig {
337                     $(
338                         $i: if self.$i.0.get() {
339                                 Some(self.$i.2.clone())
340                             } else {
341                                 None
342                             },
343                     )+
344                 }
345             }
346
347             pub fn all_options(&self) -> PartialConfig {
348                 PartialConfig {
349                     $(
350                         $i: Some(self.$i.2.clone()),
351                     )+
352                 }
353             }
354
355             pub fn override_value(&mut self, key: &str, val: &str)
356             {
357                 match key {
358                     $(
359                         stringify!($i) => {
360                             self.$i.2 = val.parse::<$ty>()
361                                 .expect(&format!("Failed to parse override for {} (\"{}\") as a {}",
362                                                  stringify!($i),
363                                                  val,
364                                                  stringify!($ty)));
365                         }
366                     )+
367                     _ => panic!("Unknown config key in override: {}", key)
368                 }
369             }
370
371             /// Construct a `Config` from the toml file specified at `file_path`.
372             ///
373             /// This method only looks at the provided path, for a method that
374             /// searches parents for a `rustfmt.toml` see `from_resolved_toml_path`.
375             ///
376             /// Return a `Config` if the config could be read and parsed from
377             /// the file, Error otherwise.
378             pub fn from_toml_path(file_path: &Path) -> Result<Config, Error> {
379                 let mut file = File::open(&file_path)?;
380                 let mut toml = String::new();
381                 file.read_to_string(&mut toml)?;
382                 Config::from_toml(&toml).map_err(|err| Error::new(ErrorKind::InvalidData, err))
383             }
384
385             /// Resolve the config for input in `dir`.
386             ///
387             /// Searches for `rustfmt.toml` beginning with `dir`, and
388             /// recursively checking parents of `dir` if no config file is found.
389             /// If no config file exists in `dir` or in any parent, a
390             /// default `Config` will be returned (and the returned path will be empty).
391             ///
392             /// Returns the `Config` to use, and the path of the project file if there was
393             /// one.
394             pub fn from_resolved_toml_path(dir: &Path) -> Result<(Config, Option<PathBuf>), Error> {
395
396                 /// Try to find a project file in the given directory and its parents.
397                 /// Returns the path of a the nearest project file if one exists,
398                 /// or `None` if no project file was found.
399                 fn resolve_project_file(dir: &Path) -> Result<Option<PathBuf>, Error> {
400                     let mut current = if dir.is_relative() {
401                         env::current_dir()?.join(dir)
402                     } else {
403                         dir.to_path_buf()
404                     };
405
406                     current = fs::canonicalize(current)?;
407
408                     loop {
409                         match get_toml_path(&current) {
410                             Ok(Some(path)) => return Ok(Some(path)),
411                             Err(e) => return Err(e),
412                             _ => ()
413                         }
414
415                         // If the current directory has no parent, we're done searching.
416                         if !current.pop() {
417                             return Ok(None);
418                         }
419                     }
420                 }
421
422                 match resolve_project_file(dir)? {
423                     None => Ok((Config::default(), None)),
424                     Some(path) => Config::from_toml_path(&path).map(|config| (config, Some(path))),
425                 }
426             }
427
428
429             pub fn print_docs() {
430                 use std::cmp;
431                 let max = 0;
432                 $( let max = cmp::max(max, stringify!($i).len()+1); )+
433                 let mut space_str = String::with_capacity(max);
434                 for _ in 0..max {
435                     space_str.push(' ');
436                 }
437                 println!("Configuration Options:");
438                 $(
439                     let name_raw = stringify!($i);
440                     let mut name_out = String::with_capacity(max);
441                     for _ in name_raw.len()..max-1 {
442                         name_out.push(' ')
443                     }
444                     name_out.push_str(name_raw);
445                     name_out.push(' ');
446                     println!("{}{} Default: {:?}",
447                              name_out,
448                              <$ty>::doc_hint(),
449                              $def);
450                     $(
451                         println!("{}{}", space_str, $dstring);
452                     )+
453                     println!("");
454                 )+
455             }
456         }
457
458         // Template for the default configuration
459         impl Default for Config {
460             fn default() -> Config {
461                 Config {
462                     $(
463                         $i: (Cell::new(false), false, $def),
464                     )+
465                 }
466             }
467         }
468     )
469 }
470
471 /// Check for the presence of known config file names (`rustfmt.toml, `.rustfmt.toml`) in `dir`
472 ///
473 /// Return the path if a config file exists, empty if no file exists, and Error for IO errors
474 pub fn get_toml_path(dir: &Path) -> Result<Option<PathBuf>, Error> {
475     const CONFIG_FILE_NAMES: [&'static str; 2] = [".rustfmt.toml", "rustfmt.toml"];
476     for config_file_name in &CONFIG_FILE_NAMES {
477         let config_file = dir.join(config_file_name);
478         match fs::metadata(&config_file) {
479             // Only return if it's a file to handle the unlikely situation of a directory named
480             // `rustfmt.toml`.
481             Ok(ref md) if md.is_file() => return Ok(Some(config_file)),
482             // Return the error if it's something other than `NotFound`; otherwise we didn't
483             // find the project file yet, and continue searching.
484             Err(e) => if e.kind() != ErrorKind::NotFound {
485                 return Err(e);
486             },
487             _ => {}
488         }
489     }
490     Ok(None)
491 }
492
493
494
495 create_config! {
496     verbose: bool, false, "Use verbose output";
497     disable_all_formatting: bool, false, "Don't reformat anything";
498     skip_children: bool, false, "Don't reformat out of line modules";
499     file_lines: FileLines, FileLines::all(),
500         "Lines to format; this is not supported in rustfmt.toml, and can only be specified \
501          via the --file-lines option";
502     max_width: usize, 100, "Maximum width of each line";
503     error_on_line_overflow: bool, true, "Error if unable to get all lines within max_width";
504     error_on_line_overflow_comments: bool, true, "Error if unable to get comments within max_width";
505     tab_spaces: usize, 4, "Number of spaces per tab";
506     fn_call_width: usize, 60,
507         "Maximum width of the args of a function call before falling back to vertical formatting";
508     struct_lit_width: usize, 18,
509         "Maximum width in the body of a struct lit before falling back to vertical formatting";
510     struct_variant_width: usize, 35,
511         "Maximum width in the body of a struct variant before falling back to vertical formatting";
512     force_explicit_abi: bool, true, "Always print the abi for extern items";
513     newline_style: NewlineStyle, NewlineStyle::Unix, "Unix or Windows line endings";
514     fn_brace_style: BraceStyle, BraceStyle::SameLineWhere, "Brace style for functions";
515     item_brace_style: BraceStyle, BraceStyle::SameLineWhere, "Brace style for structs and enums";
516     control_style: Style, Style::Rfc, "Indent style for control flow statements";
517     control_brace_style: ControlBraceStyle, ControlBraceStyle::AlwaysSameLine,
518         "Brace style for control flow constructs";
519     impl_empty_single_line: bool, true, "Put empty-body implementations on a single line";
520     trailing_comma: SeparatorTactic, SeparatorTactic::Vertical,
521         "How to handle trailing commas for lists";
522     trailing_semicolon: bool, true, "Add trailing semicolon after break, continue and return";
523     fn_empty_single_line: bool, true, "Put empty-body functions on a single line";
524     fn_single_line: bool, false, "Put single-expression functions on a single line";
525     fn_return_indent: ReturnIndent, ReturnIndent::WithArgs,
526         "Location of return type in function declaration";
527     fn_args_paren_newline: bool, false, "If function argument parenthesis goes on a newline";
528     fn_args_density: Density, Density::Tall, "Argument density in functions";
529     fn_args_layout: IndentStyle, IndentStyle::Block,
530         "Layout of function arguments and tuple structs";
531     array_layout: IndentStyle, IndentStyle::Block, "Indent on arrays";
532     array_width: usize, 60,
533         "Maximum width of an array literal before falling back to vertical formatting";
534     array_horizontal_layout_threshold: usize, 0,
535         "How many elements array must have before rustfmt uses horizontal layout.";
536     type_punctuation_density: TypeDensity, TypeDensity::Wide,
537         "Determines if '+' or '=' are wrapped in spaces in the punctuation of types";
538     where_style: Style, Style::Rfc, "Overall strategy for where clauses";
539     // TODO:
540     // 1. Should we at least try to put the where clause on the same line as the rest of the
541     // function decl?
542     // 2. Currently options `Tall` and `Vertical` produce the same output.
543     where_density: Density, Density::Vertical, "Density of a where clause";
544     where_layout: ListTactic, ListTactic::Vertical, "Element layout inside a where clause";
545     where_pred_indent: IndentStyle, IndentStyle::Visual,
546         "Indentation style of a where predicate";
547     generics_indent: IndentStyle, IndentStyle::Block, "Indentation of generics";
548     struct_lit_style: IndentStyle, IndentStyle::Block, "Style of struct definition";
549     struct_lit_multiline_style: MultilineStyle, MultilineStyle::PreferSingle,
550         "Multiline style on literal structs";
551     fn_call_style: IndentStyle, IndentStyle::Block, "Indentation for function calls, etc.";
552     report_todo: ReportTactic, ReportTactic::Never,
553         "Report all, none or unnumbered occurrences of TODO in source file comments";
554     report_fixme: ReportTactic, ReportTactic::Never,
555         "Report all, none or unnumbered occurrences of FIXME in source file comments";
556     chain_indent: IndentStyle, IndentStyle::Block, "Indentation of chain";
557     chain_one_line_max: usize, 60, "Maximum length of a chain to fit on a single line";
558     chain_split_single_child: bool, false, "Split a chain with a single child if its length \
559                                             exceeds `chain_one_line_max`";
560     imports_indent: IndentStyle, IndentStyle::Visual, "Indent of imports";
561     imports_layout: ListTactic, ListTactic::Mixed, "Item layout inside a import block";
562     reorder_extern_crates: bool, true, "Reorder extern crate statements alphabetically";
563     reorder_extern_crates_in_group: bool, true, "Reorder extern crate statements in group";
564     reorder_imports: bool, false, "Reorder import statements alphabetically";
565     reorder_imports_in_group: bool, false, "Reorder import statements in group";
566     reorder_imported_names: bool, true,
567         "Reorder lists of names in import statements alphabetically";
568     single_line_if_else_max_width: usize, 50, "Maximum line length for single line if-else \
569                                                 expressions. A value of zero means always break \
570                                                 if-else expressions.";
571     format_strings: bool, false, "Format string literals where necessary";
572     force_format_strings: bool, false, "Always format string literals";
573     take_source_hints: bool, false, "Retain some formatting characteristics from the source code";
574     hard_tabs: bool, false, "Use tab characters for indentation, spaces for alignment";
575     wrap_comments: bool, false, "Break comments to fit on the line";
576     comment_width: usize, 80, "Maximum length of comments. No effect unless wrap_comments = true";
577     normalize_comments: bool, false, "Convert /* */ comments to // comments where possible";
578     wrap_match_arms: bool, true, "Wrap the body of arms in blocks when it does not fit on \
579                                   the same line with the pattern of arms";
580     match_block_trailing_comma: bool, false,
581         "Put a trailing comma after a block based match arm (non-block arms are not affected)";
582     indent_match_arms: bool, true, "Indent match arms instead of keeping them at the same \
583                                     indentation level as the match keyword";
584     match_pattern_separator_break_point: SeparatorPlace, SeparatorPlace::Back,
585         "Put a match sub-patterns' separator in front or back.";
586     closure_block_indent_threshold: isize, 7, "How many lines a closure must have before it is \
587                                                block indented. -1 means never use block indent.";
588     space_before_type_annotation: bool, false,
589         "Leave a space before the colon in a type annotation";
590     space_after_type_annotation_colon: bool, true,
591         "Leave a space after the colon in a type annotation";
592     space_before_struct_lit_field_colon: bool, false,
593         "Leave a space before the colon in a struct literal field";
594     space_after_struct_lit_field_colon: bool, true,
595         "Leave a space after the colon in a struct literal field";
596     space_before_bound: bool, false, "Leave a space before the colon in a trait or lifetime bound";
597     space_after_bound_colon: bool, true,
598         "Leave a space after the colon in a trait or lifetime bound";
599     spaces_around_ranges: bool, false, "Put spaces around the  .. and ... range operators";
600     spaces_within_angle_brackets: bool, false, "Put spaces within non-empty generic arguments";
601     spaces_within_square_brackets: bool, false, "Put spaces within non-empty square brackets";
602     spaces_within_parens: bool, false, "Put spaces within non-empty parentheses";
603     use_try_shorthand: bool, false, "Replace uses of the try! macro by the ? shorthand";
604     write_mode: WriteMode, WriteMode::Overwrite,
605         "What Write Mode to use when none is supplied: \
606          Replace, Overwrite, Display, Plain, Diff, Coverage";
607     condense_wildcard_suffixes: bool, false, "Replace strings of _ wildcards by a single .. in \
608                                               tuple patterns";
609     combine_control_expr: bool, true, "Combine control expressions with funciton calls.";
610     struct_field_align_threshold: usize, 0, "Align struct fields if their diffs fits within \
611                                              threshold.";
612     remove_blank_lines_at_start_or_end_of_block: bool, true,
613         "Remove blank lines at start or end of a block";
614     attributes_on_same_line_as_field: bool, true,
615         "Try to put attributes on the same line as fields.";
616     attributes_on_same_line_as_variant: bool, true,
617         "Try to put attributes on the same line as variants in enum declarations.";
618     multiline_closure_forces_block: bool, false,
619         "Force multiline closure bodies to be wrapped in a block";
620     multiline_match_arm_forces_block: bool, false,
621         "Force multiline match arm bodies to be wrapped in a block";
622     merge_derives: bool, true, "Merge multiple `#[derive(...)]` into a single one";
623 }
624
625 #[cfg(test)]
626 mod test {
627     use super::Config;
628
629     #[test]
630     fn test_config_set() {
631         let mut config = Config::default();
632         config.set().verbose(false);
633         assert_eq!(config.verbose(), false);
634         config.set().verbose(true);
635         assert_eq!(config.verbose(), true);
636     }
637
638     #[test]
639     fn test_config_used_to_toml() {
640         let config = Config::default();
641
642         let verbose = config.verbose();
643         let skip_children = config.skip_children();
644
645         let used_options = config.used_options();
646         let toml = used_options.to_toml().unwrap();
647         assert_eq!(
648             toml,
649             format!("verbose = {}\nskip_children = {}\n", verbose, skip_children)
650         );
651     }
652
653     #[test]
654     fn test_was_set() {
655         let config = Config::from_toml("hard_tabs = true").unwrap();
656
657         assert_eq!(config.was_set().hard_tabs(), true);
658         assert_eq!(config.was_set().verbose(), false);
659     }
660 }