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