]> git.lizzy.rs Git - rust.git/blob - src/config/config_type.rs
Merge branch 'master' of https://github.com/rust-lang-nursery/rustfmt into fix-option...
[rust.git] / src / config / config_type.rs
1 // Copyright 2018 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 use config::file_lines::FileLines;
12 use config::options::{IgnoreList, WidthHeuristics};
13
14 /// Trait for types that can be used in `Config`.
15 pub trait ConfigType: Sized {
16     /// Returns hint text for use in `Config::print_docs()`. For enum types, this is a
17     /// pipe-separated list of variants; for other types it returns "<type>".
18     fn doc_hint() -> String;
19 }
20
21 impl ConfigType for bool {
22     fn doc_hint() -> String {
23         String::from("<boolean>")
24     }
25 }
26
27 impl ConfigType for usize {
28     fn doc_hint() -> String {
29         String::from("<unsigned integer>")
30     }
31 }
32
33 impl ConfigType for isize {
34     fn doc_hint() -> String {
35         String::from("<signed integer>")
36     }
37 }
38
39 impl ConfigType for String {
40     fn doc_hint() -> String {
41         String::from("<string>")
42     }
43 }
44
45 impl ConfigType for FileLines {
46     fn doc_hint() -> String {
47         String::from("<json>")
48     }
49 }
50
51 impl ConfigType for WidthHeuristics {
52     fn doc_hint() -> String {
53         String::new()
54     }
55 }
56
57 impl ConfigType for IgnoreList {
58     fn doc_hint() -> String {
59         String::from("[<string>,..]")
60     }
61 }
62
63 /// Check if we're in a nightly build.
64 ///
65 /// The environment variable `CFG_RELEASE_CHANNEL` is set during the rustc bootstrap
66 /// to "stable", "beta", or "nightly" depending on what toolchain is being built.
67 /// If we are being built as part of the stable or beta toolchains, we want
68 /// to disable unstable configuration options.
69 ///
70 /// If we're being built by cargo (e.g. `cargo +nightly install rustfmt-nightly`),
71 /// `CFG_RELEASE_CHANNEL` is not set. As we only support being built against the
72 /// nightly compiler when installed from crates.io, default to nightly mode.
73 macro_rules! is_nightly_channel {
74     () => {
75         option_env!("CFG_RELEASE_CHANNEL")
76             .map(|c| c == "nightly" || c == "dev")
77             .unwrap_or(true)
78     };
79 }
80
81 macro_rules! create_config {
82     ($($i:ident: $ty:ty, $def:expr, $stb:expr, $( $dstring:expr ),+ );+ $(;)*) => (
83         #[cfg(test)]
84         use std::collections::HashSet;
85         use std::io::Write;
86
87         #[derive(Clone)]
88         pub struct Config {
89             // if a license_template_path has been specified, successfully read, parsed and compiled
90             // into a regex, it will be stored here
91             pub license_template: Option<Regex>,
92             // For each config item, we store a bool indicating whether it has
93             // been accessed and the value, and a bool whether the option was
94             // manually initialised, or taken from the default,
95             $($i: (Cell<bool>, bool, $ty, bool)),+
96         }
97
98         // Just like the Config struct but with each property wrapped
99         // as Option<T>. This is used to parse a rustfmt.toml that doesn't
100         // specify all properties of `Config`.
101         // We first parse into `PartialConfig`, then create a default `Config`
102         // and overwrite the properties with corresponding values from `PartialConfig`.
103         #[derive(Deserialize, Serialize, Clone)]
104         pub struct PartialConfig {
105             $(pub $i: Option<$ty>),+
106         }
107
108         impl PartialConfig {
109             pub fn to_toml(&self) -> Result<String, String> {
110                 // Non-user-facing options can't be specified in TOML
111                 let mut cloned = self.clone();
112                 cloned.file_lines = None;
113                 cloned.verbose = None;
114                 cloned.width_heuristics = None;
115
116                 ::toml::to_string(&cloned)
117                     .map_err(|e| format!("Could not output config: {}", e.to_string()))
118             }
119         }
120
121         // Macro hygiene won't allow us to make `set_$i()` methods on Config
122         // for each item, so this struct is used to give the API to set values:
123         // `config.set().option(false)`. It's pretty ugly. Consider replacing
124         // with `config.set_option(false)` if we ever get a stable/usable
125         // `concat_idents!()`.
126         pub struct ConfigSetter<'a>(&'a mut Config);
127
128         impl<'a> ConfigSetter<'a> {
129             $(
130             pub fn $i(&mut self, value: $ty) {
131                 (self.0).$i.2 = value;
132                 match stringify!($i) {
133                     "max_width" | "use_small_heuristics" => self.0.set_heuristics(),
134                     "license_template_path" => self.0.set_license_template(),
135                     &_ => (),
136                 }
137             }
138             )+
139         }
140
141         // Query each option, returns true if the user set the option, false if
142         // a default was used.
143         pub struct ConfigWasSet<'a>(&'a Config);
144
145         impl<'a> ConfigWasSet<'a> {
146             $(
147             pub fn $i(&self) -> bool {
148                 (self.0).$i.1
149             }
150             )+
151         }
152
153         impl Config {
154             pub(crate) fn version_meets_requirement(&self) -> bool {
155                 if self.was_set().required_version() {
156                     let version = env!("CARGO_PKG_VERSION");
157                     let required_version = self.required_version();
158                     if version != required_version {
159                         println!(
160                             "Error: rustfmt version ({}) doesn't match the required version ({})",
161                             version,
162                             required_version,
163                         );
164                         return false;
165                     }
166                 }
167
168                 true
169             }
170
171             $(
172             pub fn $i(&self) -> $ty {
173                 self.$i.0.set(true);
174                 self.$i.2.clone()
175             }
176             )+
177
178             pub fn set<'a>(&'a mut self) -> ConfigSetter<'a> {
179                 ConfigSetter(self)
180             }
181
182             pub fn was_set<'a>(&'a self) -> ConfigWasSet<'a> {
183                 ConfigWasSet(self)
184             }
185
186             fn fill_from_parsed_config(mut self, parsed: PartialConfig, dir: &Path) -> Config {
187             $(
188                 if let Some(val) = parsed.$i {
189                     if self.$i.3 {
190                         self.$i.1 = true;
191                         self.$i.2 = val;
192                     } else {
193                         if is_nightly_channel!() {
194                             self.$i.1 = true;
195                             self.$i.2 = val;
196                         } else {
197                             eprintln!("Warning: can't set `{} = {:?}`, unstable features are only \
198                                        available in nightly channel.", stringify!($i), val);
199                         }
200                     }
201                 }
202             )+
203                 self.set_heuristics();
204                 self.set_license_template();
205                 self.set_ignore(dir);
206                 self
207             }
208
209             /// Returns a hash set initialized with every user-facing config option name.
210             #[cfg(test)]
211             pub(crate) fn hash_set() -> HashSet<String> {
212                 let mut hash_set = HashSet::new();
213                 $(
214                     hash_set.insert(stringify!($i).to_owned());
215                 )+
216                 hash_set
217             }
218
219             pub(crate) fn is_valid_name(name: &str) -> bool {
220                 match name {
221                     $(
222                         stringify!($i) => true,
223                     )+
224                         _ => false,
225                 }
226             }
227
228             pub(crate) fn from_toml(toml: &str, dir: &Path) -> Result<Config, String> {
229                 let parsed: ::toml::Value =
230                     toml.parse().map_err(|e| format!("Could not parse TOML: {}", e))?;
231                 let mut err: String = String::new();
232                 {
233                     let table = parsed
234                         .as_table()
235                         .ok_or(String::from("Parsed config was not table"))?;
236                     for key in table.keys() {
237                         if !Config::is_valid_name(key) {
238                             let msg = &format!("Warning: Unknown configuration option `{}`\n", key);
239                             err.push_str(msg)
240                         }
241                     }
242                 }
243                 match parsed.try_into() {
244                     Ok(parsed_config) => {
245                         if !err.is_empty() {
246                             eprint!("{}", err);
247                         }
248                         Ok(Config::default().fill_from_parsed_config(parsed_config, dir: &Path))
249                     }
250                     Err(e) => {
251                         err.push_str("Error: Decoding config file failed:\n");
252                         err.push_str(format!("{}\n", e).as_str());
253                         err.push_str("Please check your config file.");
254                         Err(err)
255                     }
256                 }
257             }
258
259             pub fn used_options(&self) -> PartialConfig {
260                 PartialConfig {
261                     $(
262                         $i: if self.$i.0.get() {
263                                 Some(self.$i.2.clone())
264                             } else {
265                                 None
266                             },
267                     )+
268                 }
269             }
270
271             pub fn all_options(&self) -> PartialConfig {
272                 PartialConfig {
273                     $(
274                         $i: Some(self.$i.2.clone()),
275                     )+
276                 }
277             }
278
279             pub fn override_value(&mut self, key: &str, val: &str)
280             {
281                 match key {
282                     $(
283                         stringify!($i) => {
284                             self.$i.1 = true;
285                             self.$i.2 = val.parse::<$ty>()
286                                 .expect(&format!("Failed to parse override for {} (\"{}\") as a {}",
287                                                  stringify!($i),
288                                                  val,
289                                                  stringify!($ty)));
290                         }
291                     )+
292                     _ => panic!("Unknown config key in override: {}", key)
293                 }
294
295                 match key {
296                     "max_width" | "use_small_heuristics" => self.set_heuristics(),
297                     "license_template_path" => self.set_license_template(),
298                     &_ => (),
299                 }
300             }
301
302             /// Construct a `Config` from the toml file specified at `file_path`.
303             ///
304             /// This method only looks at the provided path, for a method that
305             /// searches parents for a `rustfmt.toml` see `from_resolved_toml_path`.
306             ///
307             /// Return a `Config` if the config could be read and parsed from
308             /// the file, Error otherwise.
309             pub(super) fn from_toml_path(file_path: &Path) -> Result<Config, Error> {
310                 let mut file = File::open(&file_path)?;
311                 let mut toml = String::new();
312                 file.read_to_string(&mut toml)?;
313                 Config::from_toml(&toml, file_path.parent().unwrap())
314                     .map_err(|err| Error::new(ErrorKind::InvalidData, err))
315             }
316
317             /// Resolve the config for input in `dir`.
318             ///
319             /// Searches for `rustfmt.toml` beginning with `dir`, and
320             /// recursively checking parents of `dir` if no config file is found.
321             /// If no config file exists in `dir` or in any parent, a
322             /// default `Config` will be returned (and the returned path will be empty).
323             ///
324             /// Returns the `Config` to use, and the path of the project file if there was
325             /// one.
326             pub(super) fn from_resolved_toml_path(
327                 dir: &Path,
328             ) -> Result<(Config, Option<PathBuf>), Error> {
329                 /// Try to find a project file in the given directory and its parents.
330                 /// Returns the path of a the nearest project file if one exists,
331                 /// or `None` if no project file was found.
332                 fn resolve_project_file(dir: &Path) -> Result<Option<PathBuf>, Error> {
333                     let mut current = if dir.is_relative() {
334                         env::current_dir()?.join(dir)
335                     } else {
336                         dir.to_path_buf()
337                     };
338
339                     current = fs::canonicalize(current)?;
340
341                     loop {
342                         match get_toml_path(&current) {
343                             Ok(Some(path)) => return Ok(Some(path)),
344                             Err(e) => return Err(e),
345                             _ => ()
346                         }
347
348                         // If the current directory has no parent, we're done searching.
349                         if !current.pop() {
350                             return Ok(None);
351                         }
352                     }
353                 }
354
355                 match resolve_project_file(dir)? {
356                     None => Ok((Config::default(), None)),
357                     Some(path) => Config::from_toml_path(&path).map(|config| (config, Some(path))),
358                 }
359             }
360
361             pub fn is_hidden_option(name: &str) -> bool {
362                 const HIDE_OPTIONS: [&str; 4] =
363                     ["verbose", "verbose_diff", "file_lines", "width_heuristics"];
364                 HIDE_OPTIONS.contains(&name)
365             }
366
367             pub fn print_docs(out: &mut Write, include_unstable: bool) {
368                 use std::cmp;
369                 let max = 0;
370                 $( let max = cmp::max(max, stringify!($i).len()+1); )+
371                 let mut space_str = String::with_capacity(max);
372                 for _ in 0..max {
373                     space_str.push(' ');
374                 }
375                 writeln!(out, "Configuration Options:").unwrap();
376                 $(
377                     if $stb || include_unstable {
378                         let name_raw = stringify!($i);
379
380                         if !Config::is_hidden_option(name_raw) {
381                             let mut name_out = String::with_capacity(max);
382                             for _ in name_raw.len()..max-1 {
383                                 name_out.push(' ')
384                             }
385                             name_out.push_str(name_raw);
386                             name_out.push(' ');
387                             writeln!(out,
388                                     "{}{} Default: {:?}{}",
389                                     name_out,
390                                     <$ty>::doc_hint(),
391                                     $def,
392                                     if !$stb { " (unstable)" } else { "" }).unwrap();
393                             $(
394                                 writeln!(out, "{}{}", space_str, $dstring).unwrap();
395                             )+
396                             writeln!(out).unwrap();
397                         }
398                     }
399                 )+
400             }
401
402             fn set_heuristics(&mut self) {
403                 if self.use_small_heuristics.2 == Heuristics::Default {
404                     let max_width = self.max_width.2;
405                     self.set().width_heuristics(WidthHeuristics::scaled(max_width));
406                 } else if self.use_small_heuristics.2 == Heuristics::Max {
407                     let max_width = self.max_width.2;
408                     self.set().width_heuristics(WidthHeuristics::set(max_width));
409                 } else {
410                     self.set().width_heuristics(WidthHeuristics::null());
411                 }
412             }
413
414             fn set_license_template(&mut self) {
415                 if self.was_set().license_template_path() {
416                     let lt_path = self.license_template_path();
417                     match license::load_and_compile_template(&lt_path) {
418                         Ok(re) => self.license_template = Some(re),
419                         Err(msg) => eprintln!("Warning for license template file {:?}: {}",
420                                               lt_path, msg),
421                     }
422                 }
423             }
424
425             fn set_ignore(&mut self, dir: &Path) {
426                 self.ignore.2.add_prefix(dir);
427             }
428
429             /// Returns true if the config key was explicitely set and is the default value.
430             pub fn is_default(&self, key: &str) -> bool {
431                 $(
432                     if let stringify!($i) = key {
433                         return self.$i.1 && self.$i.2 == $def;
434                     }
435                  )+
436                 false
437             }
438         }
439
440         // Template for the default configuration
441         impl Default for Config {
442             fn default() -> Config {
443                 Config {
444                     license_template: None,
445                     $(
446                         $i: (Cell::new(false), false, $def, $stb),
447                     )+
448                 }
449             }
450         }
451     )
452 }