Show More
This diff has been collapsed as it changes many lines, (669 lines changed) Show them Hide them | |||
@@ -0,0 +1,669 b'' | |||
|
1 | //! Code for parsing default Mercurial config items. | |
|
2 | use itertools::Itertools; | |
|
3 | use serde::Deserialize; | |
|
4 | ||
|
5 | use crate::{errors::HgError, exit_codes, FastHashMap}; | |
|
6 | ||
|
7 | /// Corresponds to the structure of `mercurial/configitems.toml`. | |
|
8 | #[derive(Debug, Deserialize)] | |
|
9 | pub struct ConfigItems { | |
|
10 | items: Vec<DefaultConfigItem>, | |
|
11 | templates: FastHashMap<String, Vec<TemplateItem>>, | |
|
12 | #[serde(rename = "template-applications")] | |
|
13 | template_applications: Vec<TemplateApplication>, | |
|
14 | } | |
|
15 | ||
|
16 | /// Corresponds to a config item declaration in `mercurial/configitems.toml`. | |
|
17 | #[derive(Clone, Debug, PartialEq, Deserialize)] | |
|
18 | #[serde(try_from = "RawDefaultConfigItem")] | |
|
19 | pub struct DefaultConfigItem { | |
|
20 | /// Section of the config the item is in (e.g. `[merge-tools]`) | |
|
21 | section: String, | |
|
22 | /// Name of the item (e.g. `meld.gui`) | |
|
23 | name: String, | |
|
24 | /// Default value (can be dynamic, see [`DefaultConfigItemType`]) | |
|
25 | default: Option<DefaultConfigItemType>, | |
|
26 | /// If the config option is generic (e.g. `merge-tools.*`), defines | |
|
27 | /// the priority of this item relative to other generic items. | |
|
28 | /// If we're looking for <pattern>, then all generic items within the same | |
|
29 | /// section will be sorted by order of priority, and the first regex match | |
|
30 | /// against `name` is returned. | |
|
31 | #[serde(default)] | |
|
32 | priority: Option<isize>, | |
|
33 | /// Aliases, if any. Each alias is a tuple of `(section, name)` for each | |
|
34 | /// option that is aliased to this one. | |
|
35 | #[serde(default)] | |
|
36 | alias: Vec<(String, String)>, | |
|
37 | /// Whether the config item is marked as experimental | |
|
38 | #[serde(default)] | |
|
39 | experimental: bool, | |
|
40 | /// The (possibly empty) docstring for the item | |
|
41 | #[serde(default)] | |
|
42 | documentation: String, | |
|
43 | } | |
|
44 | ||
|
45 | /// Corresponds to the raw (i.e. on disk) structure of config items. Used as | |
|
46 | /// an intermediate step in deserialization. | |
|
47 | #[derive(Clone, Debug, Deserialize)] | |
|
48 | struct RawDefaultConfigItem { | |
|
49 | section: String, | |
|
50 | name: String, | |
|
51 | default: Option<toml::Value>, | |
|
52 | #[serde(rename = "default-type")] | |
|
53 | default_type: Option<String>, | |
|
54 | #[serde(default)] | |
|
55 | priority: isize, | |
|
56 | #[serde(default)] | |
|
57 | generic: bool, | |
|
58 | #[serde(default)] | |
|
59 | alias: Vec<(String, String)>, | |
|
60 | #[serde(default)] | |
|
61 | experimental: bool, | |
|
62 | #[serde(default)] | |
|
63 | documentation: String, | |
|
64 | } | |
|
65 | ||
|
66 | impl TryFrom<RawDefaultConfigItem> for DefaultConfigItem { | |
|
67 | type Error = HgError; | |
|
68 | ||
|
69 | fn try_from(value: RawDefaultConfigItem) -> Result<Self, Self::Error> { | |
|
70 | Ok(Self { | |
|
71 | section: value.section, | |
|
72 | name: value.name, | |
|
73 | default: raw_default_to_concrete( | |
|
74 | value.default_type, | |
|
75 | value.default, | |
|
76 | )?, | |
|
77 | priority: if value.generic { | |
|
78 | Some(value.priority) | |
|
79 | } else { | |
|
80 | None | |
|
81 | }, | |
|
82 | alias: value.alias, | |
|
83 | experimental: value.experimental, | |
|
84 | documentation: value.documentation, | |
|
85 | }) | |
|
86 | } | |
|
87 | } | |
|
88 | ||
|
89 | impl DefaultConfigItem { | |
|
90 | fn is_generic(&self) -> bool { | |
|
91 | self.priority.is_some() | |
|
92 | } | |
|
93 | } | |
|
94 | ||
|
95 | impl<'a> TryFrom<&'a DefaultConfigItem> for Option<&'a str> { | |
|
96 | type Error = HgError; | |
|
97 | ||
|
98 | fn try_from( | |
|
99 | value: &'a DefaultConfigItem, | |
|
100 | ) -> Result<Option<&'a str>, Self::Error> { | |
|
101 | match &value.default { | |
|
102 | Some(default) => { | |
|
103 | let err = HgError::abort( | |
|
104 | format!( | |
|
105 | "programming error: wrong query on config item '{}.{}'", | |
|
106 | value.section, | |
|
107 | value.name | |
|
108 | ), | |
|
109 | exit_codes::ABORT, | |
|
110 | Some(format!( | |
|
111 | "asked for '&str', type of default is '{}'", | |
|
112 | default.type_str() | |
|
113 | )), | |
|
114 | ); | |
|
115 | match default { | |
|
116 | DefaultConfigItemType::Primitive(toml::Value::String( | |
|
117 | s, | |
|
118 | )) => Ok(Some(s)), | |
|
119 | _ => Err(err), | |
|
120 | } | |
|
121 | } | |
|
122 | None => Ok(None), | |
|
123 | } | |
|
124 | } | |
|
125 | } | |
|
126 | ||
|
127 | impl TryFrom<&DefaultConfigItem> for Option<bool> { | |
|
128 | type Error = HgError; | |
|
129 | ||
|
130 | fn try_from(value: &DefaultConfigItem) -> Result<Self, Self::Error> { | |
|
131 | match &value.default { | |
|
132 | Some(default) => { | |
|
133 | let err = HgError::abort( | |
|
134 | format!( | |
|
135 | "programming error: wrong query on config item '{}.{}'", | |
|
136 | value.section, | |
|
137 | value.name | |
|
138 | ), | |
|
139 | exit_codes::ABORT, | |
|
140 | Some(format!( | |
|
141 | "asked for 'bool', type of default is '{}'", | |
|
142 | default.type_str() | |
|
143 | )), | |
|
144 | ); | |
|
145 | match default { | |
|
146 | DefaultConfigItemType::Primitive( | |
|
147 | toml::Value::Boolean(b), | |
|
148 | ) => Ok(Some(*b)), | |
|
149 | _ => Err(err), | |
|
150 | } | |
|
151 | } | |
|
152 | None => Ok(Some(false)), | |
|
153 | } | |
|
154 | } | |
|
155 | } | |
|
156 | ||
|
157 | impl TryFrom<&DefaultConfigItem> for Option<u32> { | |
|
158 | type Error = HgError; | |
|
159 | ||
|
160 | fn try_from(value: &DefaultConfigItem) -> Result<Self, Self::Error> { | |
|
161 | match &value.default { | |
|
162 | Some(default) => { | |
|
163 | let err = HgError::abort( | |
|
164 | format!( | |
|
165 | "programming error: wrong query on config item '{}.{}'", | |
|
166 | value.section, | |
|
167 | value.name | |
|
168 | ), | |
|
169 | exit_codes::ABORT, | |
|
170 | Some(format!( | |
|
171 | "asked for 'u32', type of default is '{}'", | |
|
172 | default.type_str() | |
|
173 | )), | |
|
174 | ); | |
|
175 | match default { | |
|
176 | DefaultConfigItemType::Primitive( | |
|
177 | toml::Value::Integer(b), | |
|
178 | ) => { | |
|
179 | Ok(Some((*b).try_into().expect("TOML integer to u32"))) | |
|
180 | } | |
|
181 | _ => Err(err), | |
|
182 | } | |
|
183 | } | |
|
184 | None => Ok(None), | |
|
185 | } | |
|
186 | } | |
|
187 | } | |
|
188 | ||
|
189 | impl TryFrom<&DefaultConfigItem> for Option<u64> { | |
|
190 | type Error = HgError; | |
|
191 | ||
|
192 | fn try_from(value: &DefaultConfigItem) -> Result<Self, Self::Error> { | |
|
193 | match &value.default { | |
|
194 | Some(default) => { | |
|
195 | let err = HgError::abort( | |
|
196 | format!( | |
|
197 | "programming error: wrong query on config item '{}.{}'", | |
|
198 | value.section, | |
|
199 | value.name | |
|
200 | ), | |
|
201 | exit_codes::ABORT, | |
|
202 | Some(format!( | |
|
203 | "asked for 'u64', type of default is '{}'", | |
|
204 | default.type_str() | |
|
205 | )), | |
|
206 | ); | |
|
207 | match default { | |
|
208 | DefaultConfigItemType::Primitive( | |
|
209 | toml::Value::Integer(b), | |
|
210 | ) => { | |
|
211 | Ok(Some((*b).try_into().expect("TOML integer to u64"))) | |
|
212 | } | |
|
213 | _ => Err(err), | |
|
214 | } | |
|
215 | } | |
|
216 | None => Ok(None), | |
|
217 | } | |
|
218 | } | |
|
219 | } | |
|
220 | ||
|
221 | /// Allows abstracting over more complex default values than just primitives. | |
|
222 | /// The former `configitems.py` contained some dynamic code that is encoded | |
|
223 | /// in this enum. | |
|
224 | #[derive(Debug, PartialEq, Clone, Deserialize)] | |
|
225 | pub enum DefaultConfigItemType { | |
|
226 | /// Some primitive type (string, integer, boolean) | |
|
227 | Primitive(toml::Value), | |
|
228 | /// A dynamic value that will be given by the code at runtime | |
|
229 | Dynamic, | |
|
230 | /// An lazily-returned array (possibly only relevant in the Python impl) | |
|
231 | /// Example: `lambda: [b"zstd", b"zlib"]` | |
|
232 | Lambda(Vec<String>), | |
|
233 | /// For now, a special case for `web.encoding` that points to the | |
|
234 | /// `encoding.encoding` module in the Python impl so that local encoding | |
|
235 | /// is correctly resolved at runtime | |
|
236 | LazyModule(String), | |
|
237 | ListType, | |
|
238 | } | |
|
239 | ||
|
240 | impl DefaultConfigItemType { | |
|
241 | pub fn type_str(&self) -> &str { | |
|
242 | match self { | |
|
243 | DefaultConfigItemType::Primitive(primitive) => { | |
|
244 | primitive.type_str() | |
|
245 | } | |
|
246 | DefaultConfigItemType::Dynamic => "dynamic", | |
|
247 | DefaultConfigItemType::Lambda(_) => "lambda", | |
|
248 | DefaultConfigItemType::LazyModule(_) => "lazy_module", | |
|
249 | DefaultConfigItemType::ListType => "list_type", | |
|
250 | } | |
|
251 | } | |
|
252 | } | |
|
253 | ||
|
254 | /// Most of the fields are shared with [`DefaultConfigItem`]. | |
|
255 | #[derive(Debug, Clone, Deserialize)] | |
|
256 | #[serde(try_from = "RawTemplateItem")] | |
|
257 | struct TemplateItem { | |
|
258 | suffix: String, | |
|
259 | default: Option<DefaultConfigItemType>, | |
|
260 | priority: Option<isize>, | |
|
261 | #[serde(default)] | |
|
262 | alias: Vec<(String, String)>, | |
|
263 | #[serde(default)] | |
|
264 | experimental: bool, | |
|
265 | #[serde(default)] | |
|
266 | documentation: String, | |
|
267 | } | |
|
268 | ||
|
269 | /// Corresponds to the raw (i.e. on disk) representation of a template item. | |
|
270 | /// Used as an intermediate step in deserialization. | |
|
271 | #[derive(Clone, Debug, Deserialize)] | |
|
272 | struct RawTemplateItem { | |
|
273 | suffix: String, | |
|
274 | default: Option<toml::Value>, | |
|
275 | #[serde(rename = "default-type")] | |
|
276 | default_type: Option<String>, | |
|
277 | #[serde(default)] | |
|
278 | priority: isize, | |
|
279 | #[serde(default)] | |
|
280 | generic: bool, | |
|
281 | #[serde(default)] | |
|
282 | alias: Vec<(String, String)>, | |
|
283 | #[serde(default)] | |
|
284 | experimental: bool, | |
|
285 | #[serde(default)] | |
|
286 | documentation: String, | |
|
287 | } | |
|
288 | ||
|
289 | impl TemplateItem { | |
|
290 | fn into_default_item( | |
|
291 | self, | |
|
292 | application: TemplateApplication, | |
|
293 | ) -> DefaultConfigItem { | |
|
294 | DefaultConfigItem { | |
|
295 | section: application.section, | |
|
296 | name: application | |
|
297 | .prefix | |
|
298 | .map(|prefix| format!("{}.{}", prefix, self.suffix)) | |
|
299 | .unwrap_or(self.suffix), | |
|
300 | default: self.default, | |
|
301 | priority: self.priority, | |
|
302 | alias: self.alias, | |
|
303 | experimental: self.experimental, | |
|
304 | documentation: self.documentation, | |
|
305 | } | |
|
306 | } | |
|
307 | } | |
|
308 | ||
|
309 | impl TryFrom<RawTemplateItem> for TemplateItem { | |
|
310 | type Error = HgError; | |
|
311 | ||
|
312 | fn try_from(value: RawTemplateItem) -> Result<Self, Self::Error> { | |
|
313 | Ok(Self { | |
|
314 | suffix: value.suffix, | |
|
315 | default: raw_default_to_concrete( | |
|
316 | value.default_type, | |
|
317 | value.default, | |
|
318 | )?, | |
|
319 | priority: if value.generic { | |
|
320 | Some(value.priority) | |
|
321 | } else { | |
|
322 | None | |
|
323 | }, | |
|
324 | alias: value.alias, | |
|
325 | experimental: value.experimental, | |
|
326 | documentation: value.documentation, | |
|
327 | }) | |
|
328 | } | |
|
329 | } | |
|
330 | ||
|
331 | /// Transforms the on-disk string-based representation of complex default types | |
|
332 | /// to the concrete [`DefaultconfigItemType`]. | |
|
333 | fn raw_default_to_concrete( | |
|
334 | default_type: Option<String>, | |
|
335 | default: Option<toml::Value>, | |
|
336 | ) -> Result<Option<DefaultConfigItemType>, HgError> { | |
|
337 | Ok(match default_type.as_deref() { | |
|
338 | None => default.as_ref().map(|default| { | |
|
339 | DefaultConfigItemType::Primitive(default.to_owned()) | |
|
340 | }), | |
|
341 | Some("dynamic") => Some(DefaultConfigItemType::Dynamic), | |
|
342 | Some("list_type") => Some(DefaultConfigItemType::ListType), | |
|
343 | Some("lambda") => match &default { | |
|
344 | Some(default) => Some(DefaultConfigItemType::Lambda( | |
|
345 | default.to_owned().try_into().map_err(|e| { | |
|
346 | HgError::abort( | |
|
347 | e.to_string(), | |
|
348 | exit_codes::ABORT, | |
|
349 | Some("Check 'mercurial/configitems.toml'".into()), | |
|
350 | ) | |
|
351 | })?, | |
|
352 | )), | |
|
353 | None => { | |
|
354 | return Err(HgError::abort( | |
|
355 | "lambda defined with no return value".to_string(), | |
|
356 | exit_codes::ABORT, | |
|
357 | Some("Check 'mercurial/configitems.toml'".into()), | |
|
358 | )) | |
|
359 | } | |
|
360 | }, | |
|
361 | Some("lazy_module") => match &default { | |
|
362 | Some(default) => { | |
|
363 | Some(DefaultConfigItemType::LazyModule(match default { | |
|
364 | toml::Value::String(module) => module.to_owned(), | |
|
365 | _ => { | |
|
366 | return Err(HgError::abort( | |
|
367 | "lazy_module module name should be a string" | |
|
368 | .to_string(), | |
|
369 | exit_codes::ABORT, | |
|
370 | Some("Check 'mercurial/configitems.toml'".into()), | |
|
371 | )) | |
|
372 | } | |
|
373 | })) | |
|
374 | } | |
|
375 | None => { | |
|
376 | return Err(HgError::abort( | |
|
377 | "lazy_module should have a default value".to_string(), | |
|
378 | exit_codes::ABORT, | |
|
379 | Some("Check 'mercurial/configitems.toml'".into()), | |
|
380 | )) | |
|
381 | } | |
|
382 | }, | |
|
383 | Some(invalid) => { | |
|
384 | return Err(HgError::abort( | |
|
385 | format!("invalid default_type '{}'", invalid), | |
|
386 | exit_codes::ABORT, | |
|
387 | Some("Check 'mercurial/configitems.toml'".into()), | |
|
388 | )) | |
|
389 | } | |
|
390 | }) | |
|
391 | } | |
|
392 | ||
|
393 | #[derive(Debug, Clone, Deserialize)] | |
|
394 | struct TemplateApplication { | |
|
395 | template: String, | |
|
396 | section: String, | |
|
397 | #[serde(default)] | |
|
398 | prefix: Option<String>, | |
|
399 | } | |
|
400 | ||
|
401 | /// Represents the (dynamic) set of default core Mercurial config items from | |
|
402 | /// `mercurial/configitems.toml`. | |
|
403 | #[derive(Clone, Debug, Default)] | |
|
404 | pub struct DefaultConfig { | |
|
405 | /// Mapping of section -> (mapping of name -> item) | |
|
406 | items: FastHashMap<String, FastHashMap<String, DefaultConfigItem>>, | |
|
407 | } | |
|
408 | ||
|
409 | impl DefaultConfig { | |
|
410 | pub fn empty() -> DefaultConfig { | |
|
411 | Self { | |
|
412 | items: Default::default(), | |
|
413 | } | |
|
414 | } | |
|
415 | ||
|
416 | /// Returns `Self`, given the contents of `mercurial/configitems.toml` | |
|
417 | #[logging_timer::time("trace")] | |
|
418 | pub fn from_contents(contents: &str) -> Result<Self, HgError> { | |
|
419 | let mut from_file: ConfigItems = | |
|
420 | toml::from_str(contents).map_err(|e| { | |
|
421 | HgError::abort( | |
|
422 | e.to_string(), | |
|
423 | exit_codes::ABORT, | |
|
424 | Some("Check 'mercurial/configitems.toml'".into()), | |
|
425 | ) | |
|
426 | })?; | |
|
427 | ||
|
428 | let mut flat_items = from_file.items; | |
|
429 | ||
|
430 | for application in from_file.template_applications.drain(..) { | |
|
431 | match from_file.templates.get(&application.template) { | |
|
432 | None => return Err( | |
|
433 | HgError::abort( | |
|
434 | format!( | |
|
435 | "template application refers to undefined template '{}'", | |
|
436 | application.template | |
|
437 | ), | |
|
438 | exit_codes::ABORT, | |
|
439 | Some("Check 'mercurial/configitems.toml'".into()) | |
|
440 | ) | |
|
441 | ), | |
|
442 | Some(template_items) => { | |
|
443 | for template_item in template_items { | |
|
444 | flat_items.push( | |
|
445 | template_item | |
|
446 | .clone() | |
|
447 | .into_default_item(application.clone()), | |
|
448 | ) | |
|
449 | } | |
|
450 | } | |
|
451 | }; | |
|
452 | } | |
|
453 | ||
|
454 | let items = flat_items.into_iter().fold( | |
|
455 | FastHashMap::default(), | |
|
456 | |mut acc, item| { | |
|
457 | acc.entry(item.section.to_owned()) | |
|
458 | .or_insert_with(|| { | |
|
459 | let mut section = FastHashMap::default(); | |
|
460 | section.insert(item.name.to_owned(), item.to_owned()); | |
|
461 | section | |
|
462 | }) | |
|
463 | .insert(item.name.to_owned(), item); | |
|
464 | acc | |
|
465 | }, | |
|
466 | ); | |
|
467 | ||
|
468 | Ok(Self { items }) | |
|
469 | } | |
|
470 | ||
|
471 | /// Return the default config item that matches `section` and `item`. | |
|
472 | pub fn get( | |
|
473 | &self, | |
|
474 | section: &[u8], | |
|
475 | item: &[u8], | |
|
476 | ) -> Option<&DefaultConfigItem> { | |
|
477 | // Core items must be valid UTF-8 | |
|
478 | let section = String::from_utf8_lossy(section); | |
|
479 | let section_map = self.items.get(section.as_ref())?; | |
|
480 | let item_name_lossy = String::from_utf8_lossy(item); | |
|
481 | match section_map.get(item_name_lossy.as_ref()) { | |
|
482 | Some(item) => Some(item), | |
|
483 | None => { | |
|
484 | for generic_item in section_map | |
|
485 | .values() | |
|
486 | .filter(|item| item.is_generic()) | |
|
487 | .sorted_by_key(|item| match item.priority { | |
|
488 | Some(priority) => (priority, &item.name), | |
|
489 | _ => unreachable!(), | |
|
490 | }) | |
|
491 | { | |
|
492 | if regex::bytes::Regex::new(&generic_item.name) | |
|
493 | .expect("invalid regex in configitems") | |
|
494 | .is_match(item) | |
|
495 | { | |
|
496 | return Some(generic_item); | |
|
497 | } | |
|
498 | } | |
|
499 | None | |
|
500 | } | |
|
501 | } | |
|
502 | } | |
|
503 | } | |
|
504 | ||
|
505 | #[cfg(test)] | |
|
506 | mod tests { | |
|
507 | use crate::config::config_items::{ | |
|
508 | DefaultConfigItem, DefaultConfigItemType, | |
|
509 | }; | |
|
510 | ||
|
511 | use super::DefaultConfig; | |
|
512 | ||
|
513 | #[test] | |
|
514 | fn test_config_read() { | |
|
515 | let contents = r#" | |
|
516 | [[items]] | |
|
517 | section = "alias" | |
|
518 | name = "abcd.*" | |
|
519 | default = 3 | |
|
520 | generic = true | |
|
521 | priority = -1 | |
|
522 | ||
|
523 | [[items]] | |
|
524 | section = "alias" | |
|
525 | name = ".*" | |
|
526 | default-type = "dynamic" | |
|
527 | generic = true | |
|
528 | ||
|
529 | [[items]] | |
|
530 | section = "cmdserver" | |
|
531 | name = "track-log" | |
|
532 | default-type = "lambda" | |
|
533 | default = [ "chgserver", "cmdserver", "repocache",] | |
|
534 | ||
|
535 | [[items]] | |
|
536 | section = "chgserver" | |
|
537 | name = "idletimeout" | |
|
538 | default = 3600 | |
|
539 | ||
|
540 | [[items]] | |
|
541 | section = "cmdserver" | |
|
542 | name = "message-encodings" | |
|
543 | default-type = "list_type" | |
|
544 | ||
|
545 | [[items]] | |
|
546 | section = "web" | |
|
547 | name = "encoding" | |
|
548 | default-type = "lazy_module" | |
|
549 | default = "encoding.encoding" | |
|
550 | ||
|
551 | [[items]] | |
|
552 | section = "command-templates" | |
|
553 | name = "graphnode" | |
|
554 | alias = [["ui", "graphnodetemplate"]] | |
|
555 | documentation = """This is a docstring. | |
|
556 | This is another line \ | |
|
557 | but this is not.""" | |
|
558 | ||
|
559 | [[items]] | |
|
560 | section = "censor" | |
|
561 | name = "policy" | |
|
562 | default = "abort" | |
|
563 | experimental = true | |
|
564 | ||
|
565 | [[template-applications]] | |
|
566 | template = "diff-options" | |
|
567 | section = "commands" | |
|
568 | prefix = "revert.interactive" | |
|
569 | ||
|
570 | [[template-applications]] | |
|
571 | template = "diff-options" | |
|
572 | section = "diff" | |
|
573 | ||
|
574 | [templates] | |
|
575 | [[templates.diff-options]] | |
|
576 | suffix = "nodates" | |
|
577 | default = false | |
|
578 | ||
|
579 | [[templates.diff-options]] | |
|
580 | suffix = "showfunc" | |
|
581 | default = false | |
|
582 | ||
|
583 | [[templates.diff-options]] | |
|
584 | suffix = "unified" | |
|
585 | "#; | |
|
586 | let res = DefaultConfig::from_contents(contents); | |
|
587 | let config = match res { | |
|
588 | Ok(config) => config, | |
|
589 | Err(e) => panic!("{}", e), | |
|
590 | }; | |
|
591 | let expected = DefaultConfigItem { | |
|
592 | section: "censor".into(), | |
|
593 | name: "policy".into(), | |
|
594 | default: Some(DefaultConfigItemType::Primitive("abort".into())), | |
|
595 | priority: None, | |
|
596 | alias: vec![], | |
|
597 | experimental: true, | |
|
598 | documentation: "".into(), | |
|
599 | }; | |
|
600 | assert_eq!(config.get(b"censor", b"policy"), Some(&expected)); | |
|
601 | ||
|
602 | // Test generic priority. The `.*` pattern is wider than `abcd.*`, but | |
|
603 | // `abcd.*` has priority, so it should match first. | |
|
604 | let expected = DefaultConfigItem { | |
|
605 | section: "alias".into(), | |
|
606 | name: "abcd.*".into(), | |
|
607 | default: Some(DefaultConfigItemType::Primitive(3.into())), | |
|
608 | priority: Some(-1), | |
|
609 | alias: vec![], | |
|
610 | experimental: false, | |
|
611 | documentation: "".into(), | |
|
612 | }; | |
|
613 | assert_eq!(config.get(b"alias", b"abcdsomething"), Some(&expected)); | |
|
614 | ||
|
615 | //... but if it doesn't, we should fallback to `.*` | |
|
616 | let expected = DefaultConfigItem { | |
|
617 | section: "alias".into(), | |
|
618 | name: ".*".into(), | |
|
619 | default: Some(DefaultConfigItemType::Dynamic), | |
|
620 | priority: Some(0), | |
|
621 | alias: vec![], | |
|
622 | experimental: false, | |
|
623 | documentation: "".into(), | |
|
624 | }; | |
|
625 | assert_eq!(config.get(b"alias", b"something"), Some(&expected)); | |
|
626 | ||
|
627 | let expected = DefaultConfigItem { | |
|
628 | section: "chgserver".into(), | |
|
629 | name: "idletimeout".into(), | |
|
630 | default: Some(DefaultConfigItemType::Primitive(3600.into())), | |
|
631 | priority: None, | |
|
632 | alias: vec![], | |
|
633 | experimental: false, | |
|
634 | documentation: "".into(), | |
|
635 | }; | |
|
636 | assert_eq!(config.get(b"chgserver", b"idletimeout"), Some(&expected)); | |
|
637 | ||
|
638 | let expected = DefaultConfigItem { | |
|
639 | section: "cmdserver".into(), | |
|
640 | name: "track-log".into(), | |
|
641 | default: Some(DefaultConfigItemType::Lambda(vec![ | |
|
642 | "chgserver".into(), | |
|
643 | "cmdserver".into(), | |
|
644 | "repocache".into(), | |
|
645 | ])), | |
|
646 | priority: None, | |
|
647 | alias: vec![], | |
|
648 | experimental: false, | |
|
649 | documentation: "".into(), | |
|
650 | }; | |
|
651 | assert_eq!(config.get(b"cmdserver", b"track-log"), Some(&expected)); | |
|
652 | ||
|
653 | let expected = DefaultConfigItem { | |
|
654 | section: "command-templates".into(), | |
|
655 | name: "graphnode".into(), | |
|
656 | default: None, | |
|
657 | priority: None, | |
|
658 | alias: vec![("ui".into(), "graphnodetemplate".into())], | |
|
659 | experimental: false, | |
|
660 | documentation: | |
|
661 | "This is a docstring.\nThis is another line but this is not." | |
|
662 | .into(), | |
|
663 | }; | |
|
664 | assert_eq!( | |
|
665 | config.get(b"command-templates", b"graphnode"), | |
|
666 | Some(&expected) | |
|
667 | ); | |
|
668 | } | |
|
669 | } |
@@ -476,6 +476,12 b' dependencies = [' | |||
|
476 | 476 | |
|
477 | 477 | [[package]] |
|
478 | 478 | name = "hashbrown" |
|
479 | version = "0.12.3" | |
|
480 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
|
481 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" | |
|
482 | ||
|
483 | [[package]] | |
|
484 | name = "hashbrown" | |
|
479 | 485 | version = "0.13.1" |
|
480 | 486 | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
481 | 487 | checksum = "33ff8ae62cd3a9102e5637afc8452c55acf3844001bd5374e0b0bd7b6616c038" |
@@ -517,7 +523,7 b' dependencies = [' | |||
|
517 | 523 | "derive_more", |
|
518 | 524 | "flate2", |
|
519 | 525 | "format-bytes", |
|
520 | "hashbrown", | |
|
526 | "hashbrown 0.13.1", | |
|
521 | 527 | "home", |
|
522 | 528 | "im-rc", |
|
523 | 529 | "itertools", |
@@ -535,9 +541,11 b' dependencies = [' | |||
|
535 | 541 | "regex", |
|
536 | 542 | "same-file", |
|
537 | 543 | "self_cell", |
|
544 | "serde", | |
|
538 | 545 | "sha-1 0.10.0", |
|
539 | 546 | "tempfile", |
|
540 | 547 | "thread_local", |
|
548 | "toml", | |
|
541 | 549 | "twox-hash", |
|
542 | 550 | "zstd", |
|
543 | 551 | ] |
@@ -610,6 +618,16 b' dependencies = [' | |||
|
610 | 618 | ] |
|
611 | 619 | |
|
612 | 620 | [[package]] |
|
621 | name = "indexmap" | |
|
622 | version = "1.9.2" | |
|
623 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
|
624 | checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" | |
|
625 | dependencies = [ | |
|
626 | "autocfg", | |
|
627 | "hashbrown 0.12.3", | |
|
628 | ] | |
|
629 | ||
|
630 | [[package]] | |
|
613 | 631 | name = "instant" |
|
614 | 632 | version = "0.1.12" |
|
615 | 633 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -749,6 +767,15 b' dependencies = [' | |||
|
749 | 767 | ] |
|
750 | 768 | |
|
751 | 769 | [[package]] |
|
770 | name = "nom8" | |
|
771 | version = "0.2.0" | |
|
772 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
|
773 | checksum = "ae01545c9c7fc4486ab7debaf2aad7003ac19431791868fb2e8066df97fad2f8" | |
|
774 | dependencies = [ | |
|
775 | "memchr", | |
|
776 | ] | |
|
777 | ||
|
778 | [[package]] | |
|
752 | 779 | name = "num-integer" |
|
753 | 780 | version = "0.1.45" |
|
754 | 781 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -1107,6 +1134,35 b' source = "registry+https://github.com/ru' | |||
|
1107 | 1134 | checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" |
|
1108 | 1135 | |
|
1109 | 1136 | [[package]] |
|
1137 | name = "serde" | |
|
1138 | version = "1.0.152" | |
|
1139 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
|
1140 | checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" | |
|
1141 | dependencies = [ | |
|
1142 | "serde_derive", | |
|
1143 | ] | |
|
1144 | ||
|
1145 | [[package]] | |
|
1146 | name = "serde_derive" | |
|
1147 | version = "1.0.152" | |
|
1148 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
|
1149 | checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" | |
|
1150 | dependencies = [ | |
|
1151 | "proc-macro2", | |
|
1152 | "quote", | |
|
1153 | "syn", | |
|
1154 | ] | |
|
1155 | ||
|
1156 | [[package]] | |
|
1157 | name = "serde_spanned" | |
|
1158 | version = "0.6.1" | |
|
1159 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
|
1160 | checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4" | |
|
1161 | dependencies = [ | |
|
1162 | "serde", | |
|
1163 | ] | |
|
1164 | ||
|
1165 | [[package]] | |
|
1110 | 1166 | name = "sha-1" |
|
1111 | 1167 | version = "0.9.8" |
|
1112 | 1168 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -1160,9 +1216,9 b' checksum = "73473c0e59e6d5812c5dfe2a064a' | |||
|
1160 | 1216 | |
|
1161 | 1217 | [[package]] |
|
1162 | 1218 | name = "syn" |
|
1163 |
version = "1.0.10 |
|
|
1219 | version = "1.0.109" | |
|
1164 | 1220 | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
1165 | checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" | |
|
1221 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" | |
|
1166 | 1222 | dependencies = [ |
|
1167 | 1223 | "proc-macro2", |
|
1168 | 1224 | "quote", |
@@ -1213,6 +1269,40 b' dependencies = [' | |||
|
1213 | 1269 | ] |
|
1214 | 1270 | |
|
1215 | 1271 | [[package]] |
|
1272 | name = "toml" | |
|
1273 | version = "0.6.0" | |
|
1274 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
|
1275 | checksum = "4fb9d890e4dc9298b70f740f615f2e05b9db37dce531f6b24fb77ac993f9f217" | |
|
1276 | dependencies = [ | |
|
1277 | "serde", | |
|
1278 | "serde_spanned", | |
|
1279 | "toml_datetime", | |
|
1280 | "toml_edit", | |
|
1281 | ] | |
|
1282 | ||
|
1283 | [[package]] | |
|
1284 | name = "toml_datetime" | |
|
1285 | version = "0.5.1" | |
|
1286 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
|
1287 | checksum = "4553f467ac8e3d374bc9a177a26801e5d0f9b211aa1673fb137a403afd1c9cf5" | |
|
1288 | dependencies = [ | |
|
1289 | "serde", | |
|
1290 | ] | |
|
1291 | ||
|
1292 | [[package]] | |
|
1293 | name = "toml_edit" | |
|
1294 | version = "0.18.1" | |
|
1295 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
|
1296 | checksum = "56c59d8dd7d0dcbc6428bf7aa2f0e823e26e43b3c9aca15bbc9475d23e5fa12b" | |
|
1297 | dependencies = [ | |
|
1298 | "indexmap", | |
|
1299 | "nom8", | |
|
1300 | "serde", | |
|
1301 | "serde_spanned", | |
|
1302 | "toml_datetime", | |
|
1303 | ] | |
|
1304 | ||
|
1305 | [[package]] | |
|
1216 | 1306 | name = "twox-hash" |
|
1217 | 1307 | version = "1.6.3" |
|
1218 | 1308 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -26,10 +26,12 b' rand_distr = "0.4.3"' | |||
|
26 | 26 | rayon = "1.7.0" |
|
27 | 27 | regex = "1.7.0" |
|
28 | 28 | self_cell = "1.0" |
|
29 | serde = { version = "1.0", features = ["derive"] } | |
|
29 | 30 | sha-1 = "0.10.0" |
|
30 | 31 | twox-hash = "1.6.3" |
|
31 | 32 | same-file = "1.0.6" |
|
32 | 33 | tempfile = "3.3.0" |
|
34 | toml = "0.6" | |
|
33 | 35 | thread_local = "1.1.4" |
|
34 | 36 | crossbeam-channel = "0.5.6" |
|
35 | 37 | log = "0.4.17" |
@@ -46,5 +48,5 b' features = ["zlib"]' | |||
|
46 | 48 | default-features = false |
|
47 | 49 | |
|
48 | 50 | [dev-dependencies] |
|
49 |
clap = { version = "4.0 |
|
|
51 | clap = { version = "~4.0", features = ["derive"] } | |
|
50 | 52 | pretty_assertions = "1.1.0" |
@@ -304,8 +304,9 b' pub enum ConfigOrigin {' | |||
|
304 | 304 | CommandLineColor, |
|
305 | 305 | /// From environment variables like `$PAGER` or `$EDITOR` |
|
306 | 306 | Environment(Vec<u8>), |
|
307 | /* TODO defaults (configitems.py) | |
|
308 | * TODO extensions | |
|
307 | /// From configitems.toml | |
|
308 | Defaults, | |
|
309 | /* TODO extensions | |
|
309 | 310 | * TODO Python resources? |
|
310 | 311 | * Others? */ |
|
311 | 312 | } |
@@ -323,6 +324,9 b' impl DisplayBytes for ConfigOrigin {' | |||
|
323 | 324 | ConfigOrigin::Tweakdefaults => { |
|
324 | 325 | write_bytes!(out, b"ui.tweakdefaults") |
|
325 | 326 | } |
|
327 | ConfigOrigin::Defaults => { | |
|
328 | write_bytes!(out, b"configitems.toml") | |
|
329 | } | |
|
326 | 330 | } |
|
327 | 331 | } |
|
328 | 332 | } |
@@ -9,14 +9,19 b'' | |||
|
9 | 9 | |
|
10 | 10 | //! Mercurial config parsing and interfaces. |
|
11 | 11 | |
|
12 | pub mod config_items; | |
|
12 | 13 | mod layer; |
|
13 | 14 | mod plain_info; |
|
14 | 15 | mod values; |
|
15 | 16 | pub use layer::{ConfigError, ConfigOrigin, ConfigParseError}; |
|
17 | use lazy_static::lazy_static; | |
|
16 | 18 | pub use plain_info::PlainInfo; |
|
17 | 19 | |
|
20 | use self::config_items::DefaultConfig; | |
|
21 | use self::config_items::DefaultConfigItem; | |
|
18 | 22 | use self::layer::ConfigLayer; |
|
19 | 23 | use self::layer::ConfigValue; |
|
24 | use crate::errors::HgError; | |
|
20 | 25 | use crate::errors::{HgResultExt, IoResultExt}; |
|
21 | 26 | use crate::utils::files::get_bytes_from_os_str; |
|
22 | 27 | use format_bytes::{write_bytes, DisplayBytes}; |
@@ -26,6 +31,14 b' use std::fmt;' | |||
|
26 | 31 | use std::path::{Path, PathBuf}; |
|
27 | 32 | use std::str; |
|
28 | 33 | |
|
34 | lazy_static! { | |
|
35 | static ref DEFAULT_CONFIG: Result<DefaultConfig, HgError> = { | |
|
36 | DefaultConfig::from_contents(include_str!( | |
|
37 | "../../../../mercurial/configitems.toml" | |
|
38 | )) | |
|
39 | }; | |
|
40 | } | |
|
41 | ||
|
29 | 42 | /// Holds the config values for the current repository |
|
30 | 43 | /// TODO update this docstring once we support more sources |
|
31 | 44 | #[derive(Clone)] |
@@ -347,13 +360,32 b' impl Config {' | |||
|
347 | 360 | self.plain = plain; |
|
348 | 361 | } |
|
349 | 362 | |
|
363 | /// Returns the default value for the given config item, if any. | |
|
364 | pub fn get_default( | |
|
365 | &self, | |
|
366 | section: &[u8], | |
|
367 | item: &[u8], | |
|
368 | ) -> Result<Option<&DefaultConfigItem>, HgError> { | |
|
369 | let default_config = DEFAULT_CONFIG.as_ref().map_err(|e| { | |
|
370 | HgError::abort( | |
|
371 | e.to_string(), | |
|
372 | crate::exit_codes::ABORT, | |
|
373 | Some("`mercurial/configitems.toml` is not valid".into()), | |
|
374 | ) | |
|
375 | })?; | |
|
376 | Ok(default_config.get(section, item)) | |
|
377 | } | |
|
378 | ||
|
350 | 379 | fn get_parse<'config, T: 'config>( |
|
351 | 380 | &'config self, |
|
352 | 381 | section: &[u8], |
|
353 | 382 | item: &[u8], |
|
354 | 383 | expected_type: &'static str, |
|
355 | 384 | parse: impl Fn(&'config [u8]) -> Option<T>, |
|
356 |
) -> Result<Option<T>, |
|
|
385 | ) -> Result<Option<T>, HgError> | |
|
386 | where | |
|
387 | Option<T>: TryFrom<&'config DefaultConfigItem, Error = HgError>, | |
|
388 | { | |
|
357 | 389 | match self.get_inner(section, item) { |
|
358 | 390 | Some((layer, v)) => match parse(&v.bytes) { |
|
359 | 391 | Some(b) => Ok(Some(b)), |
@@ -364,9 +396,15 b' impl Config {' | |||
|
364 | 396 | section: section.to_owned(), |
|
365 | 397 | item: item.to_owned(), |
|
366 | 398 | expected_type, |
|
367 |
}) |
|
|
399 | }) | |
|
400 | .into()), | |
|
368 | 401 | }, |
|
369 | None => Ok(None), | |
|
402 | None => match self.get_default(section, item)? { | |
|
403 | Some(default) => Ok(default.try_into()?), | |
|
404 | None => { | |
|
405 | Ok(None) | |
|
406 | } | |
|
407 | }, | |
|
370 | 408 | } |
|
371 | 409 | } |
|
372 | 410 | |
@@ -376,7 +414,7 b' impl Config {' | |||
|
376 | 414 | &self, |
|
377 | 415 | section: &[u8], |
|
378 | 416 | item: &[u8], |
|
379 |
) -> Result<Option<&str>, |
|
|
417 | ) -> Result<Option<&str>, HgError> { | |
|
380 | 418 | self.get_parse(section, item, "ASCII or UTF-8 string", |value| { |
|
381 | 419 | str::from_utf8(value).ok() |
|
382 | 420 | }) |
@@ -388,7 +426,7 b' impl Config {' | |||
|
388 | 426 | &self, |
|
389 | 427 | section: &[u8], |
|
390 | 428 | item: &[u8], |
|
391 |
) -> Result<Option<u32>, |
|
|
429 | ) -> Result<Option<u32>, HgError> { | |
|
392 | 430 | self.get_parse(section, item, "valid integer", |value| { |
|
393 | 431 | str::from_utf8(value).ok()?.parse().ok() |
|
394 | 432 | }) |
@@ -401,7 +439,7 b' impl Config {' | |||
|
401 | 439 | &self, |
|
402 | 440 | section: &[u8], |
|
403 | 441 | item: &[u8], |
|
404 |
) -> Result<Option<u64>, |
|
|
442 | ) -> Result<Option<u64>, HgError> { | |
|
405 | 443 | self.get_parse(section, item, "byte quantity", values::parse_byte_size) |
|
406 | 444 | } |
|
407 | 445 | |
@@ -412,7 +450,7 b' impl Config {' | |||
|
412 | 450 | &self, |
|
413 | 451 | section: &[u8], |
|
414 | 452 | item: &[u8], |
|
415 |
) -> Result<Option<bool>, |
|
|
453 | ) -> Result<Option<bool>, HgError> { | |
|
416 | 454 | self.get_parse(section, item, "boolean", values::parse_bool) |
|
417 | 455 | } |
|
418 | 456 | |
@@ -422,7 +460,7 b' impl Config {' | |||
|
422 | 460 | &self, |
|
423 | 461 | section: &[u8], |
|
424 | 462 | item: &[u8], |
|
425 |
) -> Result<bool, |
|
|
463 | ) -> Result<bool, HgError> { | |
|
426 | 464 | Ok(self.get_option(section, item)?.unwrap_or(false)) |
|
427 | 465 | } |
|
428 | 466 |
@@ -7,7 +7,8 b'' | |||
|
7 | 7 | |
|
8 | 8 | use crate::error::CommandError; |
|
9 | 9 | use crate::ui::{ |
|
10 |
format_pattern_file_warning, print_narrow_sparse_warnings, |
|
|
10 | format_pattern_file_warning, print_narrow_sparse_warnings, relative_paths, | |
|
11 | RelativePaths, Ui, | |
|
11 | 12 | }; |
|
12 | 13 | use crate::utils::path_utils::RelativizePaths; |
|
13 | 14 | use clap::Arg; |
@@ -360,13 +361,26 b' pub fn run(invocation: &crate::CliInvoca' | |||
|
360 | 361 | } |
|
361 | 362 | } |
|
362 | 363 | } |
|
363 | let relative_paths = config | |
|
364 | ||
|
365 | let relative_status = config | |
|
364 | 366 | .get_option(b"commands", b"status.relative")? |
|
365 | .unwrap_or(config.get_bool(b"ui", b"relative-paths")?); | |
|
367 | .expect("commands.status.relative should have a default value"); | |
|
368 | ||
|
369 | let relativize_paths = relative_status || { | |
|
370 | // TODO should be dependent on whether patterns are passed once | |
|
371 | // we support those. | |
|
372 | // See in Python code with `getuipathfn` usage in `commands.py`. | |
|
373 | let legacy_relative_behavior = false; | |
|
374 | match relative_paths(invocation.config)? { | |
|
375 | RelativePaths::Legacy => legacy_relative_behavior, | |
|
376 | RelativePaths::Bool(v) => v, | |
|
377 | } | |
|
378 | }; | |
|
379 | ||
|
366 | 380 | let output = DisplayStatusPaths { |
|
367 | 381 | ui, |
|
368 | 382 | no_status, |
|
369 | relativize: if relative_paths { | |
|
383 | relativize: if relativize_paths { | |
|
370 | 384 | Some(RelativizePaths::new(repo)?) |
|
371 | 385 | } else { |
|
372 | 386 | None |
|
1 | NO CONTENT: file was removed |
General Comments 0
You need to be logged in to leave comments.
Login now