]> git.lizzy.rs Git - rust.git/blob - src/config/config_type.rs
Merge pull request #3535 from xiongmao86/issue3417
[rust.git] / src / config / config_type.rs
1 use crate::config::file_lines::FileLines;
2 use crate::config::options::{IgnoreList, WidthHeuristics};
3
4 /// Trait for types that can be used in `Config`.
5 pub(crate) trait ConfigType: Sized {
6     /// Returns hint text for use in `Config::print_docs()`. For enum types, this is a
7     /// pipe-separated list of variants; for other types it returns "<type>".
8     fn doc_hint() -> String;
9 }
10
11 impl ConfigType for bool {
12     fn doc_hint() -> String {
13         String::from("<boolean>")
14     }
15 }
16
17 impl ConfigType for usize {
18     fn doc_hint() -> String {
19         String::from("<unsigned integer>")
20     }
21 }
22
23 impl ConfigType for isize {
24     fn doc_hint() -> String {
25         String::from("<signed integer>")
26     }
27 }
28
29 impl ConfigType for String {
30     fn doc_hint() -> String {
31         String::from("<string>")
32     }
33 }
34
35 impl ConfigType for FileLines {
36     fn doc_hint() -> String {
37         String::from("<json>")
38     }
39 }
40
41 impl ConfigType for WidthHeuristics {
42     fn doc_hint() -> String {
43         String::new()
44     }
45 }
46
47 impl ConfigType for IgnoreList {
48     fn doc_hint() -> String {
49         String::from("[<string>,..]")
50     }
51 }
52
53 /// Checks if we're in a nightly build.
54 ///
55 /// The environment variable `CFG_RELEASE_CHANNEL` is set during the rustc bootstrap
56 /// to "stable", "beta", or "nightly" depending on what toolchain is being built.
57 /// If we are being built as part of the stable or beta toolchains, we want
58 /// to disable unstable configuration options.
59 ///
60 /// If we're being built by cargo (e.g., `cargo +nightly install rustfmt-nightly`),
61 /// `CFG_RELEASE_CHANNEL` is not set. As we only support being built against the
62 /// nightly compiler when installed from crates.io, default to nightly mode.
63 macro_rules! is_nightly_channel {
64     () => {
65         option_env!("CFG_RELEASE_CHANNEL").map_or(true, |c| c == "nightly" || c == "dev")
66     };
67 }
68
69 macro_rules! create_config {
70     ($($i:ident: $ty:ty, $def:expr, $stb:expr, $( $dstring:expr ),+ );+ $(;)*) => (
71         #[cfg(test)]
72         use std::collections::HashSet;
73         use std::io::Write;
74
75         use serde::{Deserialize, Serialize};
76
77         #[derive(Clone)]
78         #[allow(unreachable_pub)]
79         pub struct Config {
80             // if a license_template_path has been specified, successfully read, parsed and compiled
81             // into a regex, it will be stored here
82             pub license_template: Option<Regex>,
83             // For each config item, we store a bool indicating whether it has
84             // been accessed and the value, and a bool whether the option was
85             // manually initialised, or taken from the default,
86             $($i: (Cell<bool>, bool, $ty, bool)),+
87         }
88
89         // Just like the Config struct but with each property wrapped
90         // as Option<T>. This is used to parse a rustfmt.toml that doesn't
91         // specify all properties of `Config`.
92         // We first parse into `PartialConfig`, then create a default `Config`
93         // and overwrite the properties with corresponding values from `PartialConfig`.
94         #[derive(Deserialize, Serialize, Clone)]
95         #[allow(unreachable_pub)]
96         pub struct PartialConfig {
97             $(pub $i: Option<$ty>),+
98         }
99
100         // Macro hygiene won't allow us to make `set_$i()` methods on Config
101         // for each item, so this struct is used to give the API to set values:
102         // `config.set().option(false)`. It's pretty ugly. Consider replacing
103         // with `config.set_option(false)` if we ever get a stable/usable
104         // `concat_idents!()`.
105         #[allow(unreachable_pub)]
106         pub struct ConfigSetter<'a>(&'a mut Config);
107
108         impl<'a> ConfigSetter<'a> {
109             $(
110             #[allow(unreachable_pub)]
111             pub fn $i(&mut self, value: $ty) {
112                 (self.0).$i.2 = value;
113                 match stringify!($i) {
114                     "max_width" | "use_small_heuristics" => self.0.set_heuristics(),
115                     "license_template_path" => self.0.set_license_template(),
116                     &_ => (),
117                 }
118             }
119             )+
120         }
121
122         // Query each option, returns true if the user set the option, false if
123         // a default was used.
124         #[allow(unreachable_pub)]
125         pub struct ConfigWasSet<'a>(&'a Config);
126
127         impl<'a> ConfigWasSet<'a> {
128             $(
129             #[allow(unreachable_pub)]
130             pub fn $i(&self) -> bool {
131                 (self.0).$i.1
132             }
133             )+
134         }
135
136         impl Config {
137             $(
138             #[allow(unreachable_pub)]
139             pub fn $i(&self) -> $ty {
140                 self.$i.0.set(true);
141                 self.$i.2.clone()
142             }
143             )+
144
145             #[allow(unreachable_pub)]
146             pub fn set(&mut self) -> ConfigSetter<'_> {
147                 ConfigSetter(self)
148             }
149
150             #[allow(unreachable_pub)]
151             pub fn was_set(&self) -> ConfigWasSet<'_> {
152                 ConfigWasSet(self)
153             }
154
155             fn fill_from_parsed_config(mut self, parsed: PartialConfig, dir: &Path) -> Config {
156             $(
157                 if let Some(val) = parsed.$i {
158                     if self.$i.3 {
159                         self.$i.1 = true;
160                         self.$i.2 = val;
161                     } else {
162                         if is_nightly_channel!() {
163                             self.$i.1 = true;
164                             self.$i.2 = val;
165                         } else {
166                             eprintln!("Warning: can't set `{} = {:?}`, unstable features are only \
167                                        available in nightly channel.", stringify!($i), val);
168                         }
169                     }
170                 }
171             )+
172                 self.set_heuristics();
173                 self.set_license_template();
174                 self.set_ignore(dir);
175                 self
176             }
177
178             /// Returns a hash set initialized with every user-facing config option name.
179             #[cfg(test)]
180             pub(crate) fn hash_set() -> HashSet<String> {
181                 let mut hash_set = HashSet::new();
182                 $(
183                     hash_set.insert(stringify!($i).to_owned());
184                 )+
185                 hash_set
186             }
187
188             pub(crate) fn is_valid_name(name: &str) -> bool {
189                 match name {
190                     $(
191                         stringify!($i) => true,
192                     )+
193                         _ => false,
194                 }
195             }
196
197             #[allow(unreachable_pub)]
198             pub fn used_options(&self) -> PartialConfig {
199                 PartialConfig {
200                     $(
201                         $i: if self.$i.0.get() {
202                                 Some(self.$i.2.clone())
203                             } else {
204                                 None
205                             },
206                     )+
207                 }
208             }
209
210             #[allow(unreachable_pub)]
211             pub fn all_options(&self) -> PartialConfig {
212                 PartialConfig {
213                     $(
214                         $i: Some(self.$i.2.clone()),
215                     )+
216                 }
217             }
218
219             #[allow(unreachable_pub)]
220             pub fn override_value(&mut self, key: &str, val: &str)
221             {
222                 match key {
223                     $(
224                         stringify!($i) => {
225                             self.$i.1 = true;
226                             self.$i.2 = val.parse::<$ty>()
227                                 .expect(&format!("Failed to parse override for {} (\"{}\") as a {}",
228                                                  stringify!($i),
229                                                  val,
230                                                  stringify!($ty)));
231                         }
232                     )+
233                     _ => panic!("Unknown config key in override: {}", key)
234                 }
235
236                 match key {
237                     "max_width" | "use_small_heuristics" => self.set_heuristics(),
238                     "license_template_path" => self.set_license_template(),
239                     &_ => (),
240                 }
241             }
242
243             #[allow(unreachable_pub)]
244             pub fn is_hidden_option(name: &str) -> bool {
245                 const HIDE_OPTIONS: [&str; 4] =
246                     ["verbose", "verbose_diff", "file_lines", "width_heuristics"];
247                 HIDE_OPTIONS.contains(&name)
248             }
249
250             #[allow(unreachable_pub)]
251             pub fn print_docs(out: &mut dyn Write, include_unstable: bool) {
252                 use std::cmp;
253                 let max = 0;
254                 $( let max = cmp::max(max, stringify!($i).len()+1); )+
255                 let space_str = " ".repeat(max);
256                 writeln!(out, "Configuration Options:").unwrap();
257                 $(
258                     if $stb || include_unstable {
259                         let name_raw = stringify!($i);
260
261                         if !Config::is_hidden_option(name_raw) {
262                             let mut name_out = String::with_capacity(max);
263                             for _ in name_raw.len()..max-1 {
264                                 name_out.push(' ')
265                             }
266                             name_out.push_str(name_raw);
267                             name_out.push(' ');
268                             writeln!(out,
269                                     "{}{} Default: {:?}{}",
270                                     name_out,
271                                     <$ty>::doc_hint(),
272                                     $def,
273                                     if !$stb { " (unstable)" } else { "" }).unwrap();
274                             $(
275                                 writeln!(out, "{}{}", space_str, $dstring).unwrap();
276                             )+
277                             writeln!(out).unwrap();
278                         }
279                     }
280                 )+
281             }
282
283             fn set_heuristics(&mut self) {
284                 if self.use_small_heuristics.2 == Heuristics::Default {
285                     let max_width = self.max_width.2;
286                     self.set().width_heuristics(WidthHeuristics::scaled(max_width));
287                 } else if self.use_small_heuristics.2 == Heuristics::Max {
288                     let max_width = self.max_width.2;
289                     self.set().width_heuristics(WidthHeuristics::set(max_width));
290                 } else {
291                     self.set().width_heuristics(WidthHeuristics::null());
292                 }
293             }
294
295             fn set_license_template(&mut self) {
296                 if self.was_set().license_template_path() {
297                     let lt_path = self.license_template_path();
298                     match license::load_and_compile_template(&lt_path) {
299                         Ok(re) => self.license_template = Some(re),
300                         Err(msg) => eprintln!("Warning for license template file {:?}: {}",
301                                               lt_path, msg),
302                     }
303                 }
304             }
305
306             fn set_ignore(&mut self, dir: &Path) {
307                 self.ignore.2.add_prefix(dir);
308             }
309
310             #[allow(unreachable_pub)]
311             /// Returns `true` if the config key was explicitly set and is the default value.
312             pub fn is_default(&self, key: &str) -> bool {
313                 $(
314                     if let stringify!($i) = key {
315                         return self.$i.1 && self.$i.2 == $def;
316                     }
317                  )+
318                 false
319             }
320         }
321
322         // Template for the default configuration
323         impl Default for Config {
324             fn default() -> Config {
325                 Config {
326                     license_template: None,
327                     $(
328                         $i: (Cell::new(false), false, $def, $stb),
329                     )+
330                 }
331             }
332         }
333     )
334 }