]>
Commit | Line | Data |
---|---|---|
f20569fa XL |
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 | macro_rules! create_config { | |
54 | ($($i:ident: $ty:ty, $def:expr, $stb:expr, $( $dstring:expr ),+ );+ $(;)*) => ( | |
55 | #[cfg(test)] | |
56 | use std::collections::HashSet; | |
57 | use std::io::Write; | |
58 | ||
59 | use serde::{Deserialize, Serialize}; | |
60 | ||
61 | #[derive(Clone)] | |
62 | #[allow(unreachable_pub)] | |
63 | pub struct Config { | |
64 | // if a license_template_path has been specified, successfully read, parsed and compiled | |
65 | // into a regex, it will be stored here | |
66 | pub license_template: Option<Regex>, | |
67 | // For each config item, we store a bool indicating whether it has | |
68 | // been accessed and the value, and a bool whether the option was | |
69 | // manually initialised, or taken from the default, | |
70 | $($i: (Cell<bool>, bool, $ty, bool)),+ | |
71 | } | |
72 | ||
73 | // Just like the Config struct but with each property wrapped | |
74 | // as Option<T>. This is used to parse a rustfmt.toml that doesn't | |
75 | // specify all properties of `Config`. | |
76 | // We first parse into `PartialConfig`, then create a default `Config` | |
77 | // and overwrite the properties with corresponding values from `PartialConfig`. | |
78 | #[derive(Deserialize, Serialize, Clone)] | |
79 | #[allow(unreachable_pub)] | |
80 | pub struct PartialConfig { | |
81 | $(pub $i: Option<$ty>),+ | |
82 | } | |
83 | ||
84 | // Macro hygiene won't allow us to make `set_$i()` methods on Config | |
85 | // for each item, so this struct is used to give the API to set values: | |
86 | // `config.set().option(false)`. It's pretty ugly. Consider replacing | |
87 | // with `config.set_option(false)` if we ever get a stable/usable | |
88 | // `concat_idents!()`. | |
89 | #[allow(unreachable_pub)] | |
90 | pub struct ConfigSetter<'a>(&'a mut Config); | |
91 | ||
92 | impl<'a> ConfigSetter<'a> { | |
93 | $( | |
94 | #[allow(unreachable_pub)] | |
95 | pub fn $i(&mut self, value: $ty) { | |
96 | (self.0).$i.2 = value; | |
97 | match stringify!($i) { | |
98 | "max_width" | "use_small_heuristics" => self.0.set_heuristics(), | |
99 | "license_template_path" => self.0.set_license_template(), | |
100 | "merge_imports" => self.0.set_merge_imports(), | |
101 | &_ => (), | |
102 | } | |
103 | } | |
104 | )+ | |
105 | } | |
106 | ||
107 | // Query each option, returns true if the user set the option, false if | |
108 | // a default was used. | |
109 | #[allow(unreachable_pub)] | |
110 | pub struct ConfigWasSet<'a>(&'a Config); | |
111 | ||
112 | impl<'a> ConfigWasSet<'a> { | |
113 | $( | |
114 | #[allow(unreachable_pub)] | |
115 | pub fn $i(&self) -> bool { | |
116 | (self.0).$i.1 | |
117 | } | |
118 | )+ | |
119 | } | |
120 | ||
121 | impl Config { | |
122 | $( | |
123 | #[allow(unreachable_pub)] | |
124 | pub fn $i(&self) -> $ty { | |
125 | self.$i.0.set(true); | |
126 | self.$i.2.clone() | |
127 | } | |
128 | )+ | |
129 | ||
130 | #[allow(unreachable_pub)] | |
131 | pub fn set(&mut self) -> ConfigSetter<'_> { | |
132 | ConfigSetter(self) | |
133 | } | |
134 | ||
135 | #[allow(unreachable_pub)] | |
136 | pub fn was_set(&self) -> ConfigWasSet<'_> { | |
137 | ConfigWasSet(self) | |
138 | } | |
139 | ||
140 | fn fill_from_parsed_config(mut self, parsed: PartialConfig, dir: &Path) -> Config { | |
141 | $( | |
142 | if let Some(val) = parsed.$i { | |
143 | if self.$i.3 { | |
144 | self.$i.1 = true; | |
145 | self.$i.2 = val; | |
146 | } else { | |
147 | if crate::is_nightly_channel!() { | |
148 | self.$i.1 = true; | |
149 | self.$i.2 = val; | |
150 | } else { | |
151 | eprintln!("Warning: can't set `{} = {:?}`, unstable features are only \ | |
152 | available in nightly channel.", stringify!($i), val); | |
153 | } | |
154 | } | |
155 | } | |
156 | )+ | |
157 | self.set_heuristics(); | |
158 | self.set_license_template(); | |
159 | self.set_ignore(dir); | |
160 | self.set_merge_imports(); | |
161 | self | |
162 | } | |
163 | ||
164 | /// Returns a hash set initialized with every user-facing config option name. | |
165 | #[cfg(test)] | |
166 | pub(crate) fn hash_set() -> HashSet<String> { | |
167 | let mut hash_set = HashSet::new(); | |
168 | $( | |
169 | hash_set.insert(stringify!($i).to_owned()); | |
170 | )+ | |
171 | hash_set | |
172 | } | |
173 | ||
174 | pub(crate) fn is_valid_name(name: &str) -> bool { | |
175 | match name { | |
176 | $( | |
177 | stringify!($i) => true, | |
178 | )+ | |
179 | _ => false, | |
180 | } | |
181 | } | |
182 | ||
183 | #[allow(unreachable_pub)] | |
184 | pub fn is_valid_key_val(key: &str, val: &str) -> bool { | |
185 | match key { | |
186 | $( | |
187 | stringify!($i) => val.parse::<$ty>().is_ok(), | |
188 | )+ | |
189 | _ => false, | |
190 | } | |
191 | } | |
192 | ||
193 | #[allow(unreachable_pub)] | |
194 | pub fn used_options(&self) -> PartialConfig { | |
195 | PartialConfig { | |
196 | $( | |
197 | $i: if self.$i.0.get() { | |
198 | Some(self.$i.2.clone()) | |
199 | } else { | |
200 | None | |
201 | }, | |
202 | )+ | |
203 | } | |
204 | } | |
205 | ||
206 | #[allow(unreachable_pub)] | |
207 | pub fn all_options(&self) -> PartialConfig { | |
208 | PartialConfig { | |
209 | $( | |
210 | $i: Some(self.$i.2.clone()), | |
211 | )+ | |
212 | } | |
213 | } | |
214 | ||
215 | #[allow(unreachable_pub)] | |
216 | pub fn override_value(&mut self, key: &str, val: &str) | |
217 | { | |
218 | match key { | |
219 | $( | |
220 | stringify!($i) => { | |
221 | self.$i.1 = true; | |
222 | self.$i.2 = val.parse::<$ty>() | |
223 | .expect(&format!("Failed to parse override for {} (\"{}\") as a {}", | |
224 | stringify!($i), | |
225 | val, | |
226 | stringify!($ty))); | |
227 | } | |
228 | )+ | |
229 | _ => panic!("Unknown config key in override: {}", key) | |
230 | } | |
231 | ||
232 | match key { | |
233 | "max_width" | "use_small_heuristics" => self.set_heuristics(), | |
234 | "license_template_path" => self.set_license_template(), | |
235 | "merge_imports" => self.set_merge_imports(), | |
236 | &_ => (), | |
237 | } | |
238 | } | |
239 | ||
240 | #[allow(unreachable_pub)] | |
241 | pub fn is_hidden_option(name: &str) -> bool { | |
242 | const HIDE_OPTIONS: [&str; 5] = | |
243 | ["verbose", "verbose_diff", "file_lines", "width_heuristics", "merge_imports"]; | |
244 | HIDE_OPTIONS.contains(&name) | |
245 | } | |
246 | ||
247 | #[allow(unreachable_pub)] | |
248 | pub fn print_docs(out: &mut dyn Write, include_unstable: bool) { | |
249 | use std::cmp; | |
250 | let max = 0; | |
251 | $( let max = cmp::max(max, stringify!($i).len()+1); )+ | |
252 | let space_str = " ".repeat(max); | |
253 | writeln!(out, "Configuration Options:").unwrap(); | |
254 | $( | |
255 | if $stb || include_unstable { | |
256 | let name_raw = stringify!($i); | |
257 | ||
258 | if !Config::is_hidden_option(name_raw) { | |
259 | let mut name_out = String::with_capacity(max); | |
260 | for _ in name_raw.len()..max-1 { | |
261 | name_out.push(' ') | |
262 | } | |
263 | name_out.push_str(name_raw); | |
264 | name_out.push(' '); | |
265 | let mut default_str = format!("{}", $def); | |
266 | if default_str.is_empty() { | |
267 | default_str = String::from("\"\""); | |
268 | } | |
269 | writeln!(out, | |
270 | "{}{} Default: {}{}", | |
271 | name_out, | |
272 | <$ty>::doc_hint(), | |
273 | default_str, | |
274 | if !$stb { " (unstable)" } else { "" }).unwrap(); | |
275 | $( | |
276 | writeln!(out, "{}{}", space_str, $dstring).unwrap(); | |
277 | )+ | |
278 | writeln!(out).unwrap(); | |
279 | } | |
280 | } | |
281 | )+ | |
282 | } | |
283 | ||
284 | fn set_heuristics(&mut self) { | |
285 | if self.use_small_heuristics.2 == Heuristics::Default { | |
286 | let max_width = self.max_width.2; | |
287 | self.set().width_heuristics(WidthHeuristics::scaled(max_width)); | |
288 | } else if self.use_small_heuristics.2 == Heuristics::Max { | |
289 | let max_width = self.max_width.2; | |
290 | self.set().width_heuristics(WidthHeuristics::set(max_width)); | |
291 | } else { | |
292 | self.set().width_heuristics(WidthHeuristics::null()); | |
293 | } | |
294 | } | |
295 | ||
296 | fn set_license_template(&mut self) { | |
297 | if self.was_set().license_template_path() { | |
298 | let lt_path = self.license_template_path(); | |
299 | if lt_path.len() > 0 { | |
300 | match license::load_and_compile_template(<_path) { | |
301 | Ok(re) => self.license_template = Some(re), | |
302 | Err(msg) => eprintln!("Warning for license template file {:?}: {}", | |
303 | lt_path, msg), | |
304 | } | |
305 | } else { | |
306 | self.license_template = None; | |
307 | } | |
308 | } | |
309 | } | |
310 | ||
311 | fn set_ignore(&mut self, dir: &Path) { | |
312 | self.ignore.2.add_prefix(dir); | |
313 | } | |
314 | ||
315 | fn set_merge_imports(&mut self) { | |
316 | if self.was_set().merge_imports() { | |
317 | eprintln!( | |
318 | "Warning: the `merge_imports` option is deprecated. \ | |
319 | Use `imports_granularity=Crate` instead" | |
320 | ); | |
321 | if !self.was_set().imports_granularity() { | |
322 | self.imports_granularity.2 = if self.merge_imports() { | |
323 | ImportGranularity::Crate | |
324 | } else { | |
325 | ImportGranularity::Preserve | |
326 | }; | |
327 | } | |
328 | } | |
329 | } | |
330 | ||
331 | #[allow(unreachable_pub)] | |
332 | /// Returns `true` if the config key was explicitly set and is the default value. | |
333 | pub fn is_default(&self, key: &str) -> bool { | |
334 | $( | |
335 | if let stringify!($i) = key { | |
336 | return self.$i.1 && self.$i.2 == $def; | |
337 | } | |
338 | )+ | |
339 | false | |
340 | } | |
341 | } | |
342 | ||
343 | // Template for the default configuration | |
344 | impl Default for Config { | |
345 | fn default() -> Config { | |
346 | Config { | |
347 | license_template: None, | |
348 | $( | |
349 | $i: (Cell::new(false), false, $def, $stb), | |
350 | )+ | |
351 | } | |
352 | } | |
353 | } | |
354 | ) | |
355 | } |