##// END OF EJS Templates
rust-config: add support for default config items...
Raphaël Gomès -
r51656:f8412da8 default
parent child Browse files
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 [[package]]
477 [[package]]
478 name = "hashbrown"
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 version = "0.13.1"
485 version = "0.13.1"
480 source = "registry+https://github.com/rust-lang/crates.io-index"
486 source = "registry+https://github.com/rust-lang/crates.io-index"
481 checksum = "33ff8ae62cd3a9102e5637afc8452c55acf3844001bd5374e0b0bd7b6616c038"
487 checksum = "33ff8ae62cd3a9102e5637afc8452c55acf3844001bd5374e0b0bd7b6616c038"
@@ -517,7 +523,7 b' dependencies = ['
517 "derive_more",
523 "derive_more",
518 "flate2",
524 "flate2",
519 "format-bytes",
525 "format-bytes",
520 "hashbrown",
526 "hashbrown 0.13.1",
521 "home",
527 "home",
522 "im-rc",
528 "im-rc",
523 "itertools",
529 "itertools",
@@ -535,9 +541,11 b' dependencies = ['
535 "regex",
541 "regex",
536 "same-file",
542 "same-file",
537 "self_cell",
543 "self_cell",
544 "serde",
538 "sha-1 0.10.0",
545 "sha-1 0.10.0",
539 "tempfile",
546 "tempfile",
540 "thread_local",
547 "thread_local",
548 "toml",
541 "twox-hash",
549 "twox-hash",
542 "zstd",
550 "zstd",
543 ]
551 ]
@@ -610,6 +618,16 b' dependencies = ['
610 ]
618 ]
611
619
612 [[package]]
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 name = "instant"
631 name = "instant"
614 version = "0.1.12"
632 version = "0.1.12"
615 source = "registry+https://github.com/rust-lang/crates.io-index"
633 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -749,6 +767,15 b' dependencies = ['
749 ]
767 ]
750
768
751 [[package]]
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 name = "num-integer"
779 name = "num-integer"
753 version = "0.1.45"
780 version = "0.1.45"
754 source = "registry+https://github.com/rust-lang/crates.io-index"
781 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1107,6 +1134,35 b' source = "registry+https://github.com/ru'
1107 checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4"
1134 checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4"
1108
1135
1109 [[package]]
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 name = "sha-1"
1166 name = "sha-1"
1111 version = "0.9.8"
1167 version = "0.9.8"
1112 source = "registry+https://github.com/rust-lang/crates.io-index"
1168 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1160,9 +1216,9 b' checksum = "73473c0e59e6d5812c5dfe2a064a'
1160
1216
1161 [[package]]
1217 [[package]]
1162 name = "syn"
1218 name = "syn"
1163 version = "1.0.103"
1219 version = "1.0.109"
1164 source = "registry+https://github.com/rust-lang/crates.io-index"
1220 source = "registry+https://github.com/rust-lang/crates.io-index"
1165 checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d"
1221 checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
1166 dependencies = [
1222 dependencies = [
1167 "proc-macro2",
1223 "proc-macro2",
1168 "quote",
1224 "quote",
@@ -1213,6 +1269,40 b' dependencies = ['
1213 ]
1269 ]
1214
1270
1215 [[package]]
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 name = "twox-hash"
1306 name = "twox-hash"
1217 version = "1.6.3"
1307 version = "1.6.3"
1218 source = "registry+https://github.com/rust-lang/crates.io-index"
1308 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -26,10 +26,12 b' rand_distr = "0.4.3"'
26 rayon = "1.7.0"
26 rayon = "1.7.0"
27 regex = "1.7.0"
27 regex = "1.7.0"
28 self_cell = "1.0"
28 self_cell = "1.0"
29 serde = { version = "1.0", features = ["derive"] }
29 sha-1 = "0.10.0"
30 sha-1 = "0.10.0"
30 twox-hash = "1.6.3"
31 twox-hash = "1.6.3"
31 same-file = "1.0.6"
32 same-file = "1.0.6"
32 tempfile = "3.3.0"
33 tempfile = "3.3.0"
34 toml = "0.6"
33 thread_local = "1.1.4"
35 thread_local = "1.1.4"
34 crossbeam-channel = "0.5.6"
36 crossbeam-channel = "0.5.6"
35 log = "0.4.17"
37 log = "0.4.17"
@@ -46,5 +48,5 b' features = ["zlib"]'
46 default-features = false
48 default-features = false
47
49
48 [dev-dependencies]
50 [dev-dependencies]
49 clap = { version = "4.0.24", features = ["derive"] }
51 clap = { version = "~4.0", features = ["derive"] }
50 pretty_assertions = "1.1.0"
52 pretty_assertions = "1.1.0"
@@ -304,8 +304,9 b' pub enum ConfigOrigin {'
304 CommandLineColor,
304 CommandLineColor,
305 /// From environment variables like `$PAGER` or `$EDITOR`
305 /// From environment variables like `$PAGER` or `$EDITOR`
306 Environment(Vec<u8>),
306 Environment(Vec<u8>),
307 /* TODO defaults (configitems.py)
307 /// From configitems.toml
308 * TODO extensions
308 Defaults,
309 /* TODO extensions
309 * TODO Python resources?
310 * TODO Python resources?
310 * Others? */
311 * Others? */
311 }
312 }
@@ -323,6 +324,9 b' impl DisplayBytes for ConfigOrigin {'
323 ConfigOrigin::Tweakdefaults => {
324 ConfigOrigin::Tweakdefaults => {
324 write_bytes!(out, b"ui.tweakdefaults")
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 //! Mercurial config parsing and interfaces.
10 //! Mercurial config parsing and interfaces.
11
11
12 pub mod config_items;
12 mod layer;
13 mod layer;
13 mod plain_info;
14 mod plain_info;
14 mod values;
15 mod values;
15 pub use layer::{ConfigError, ConfigOrigin, ConfigParseError};
16 pub use layer::{ConfigError, ConfigOrigin, ConfigParseError};
17 use lazy_static::lazy_static;
16 pub use plain_info::PlainInfo;
18 pub use plain_info::PlainInfo;
17
19
20 use self::config_items::DefaultConfig;
21 use self::config_items::DefaultConfigItem;
18 use self::layer::ConfigLayer;
22 use self::layer::ConfigLayer;
19 use self::layer::ConfigValue;
23 use self::layer::ConfigValue;
24 use crate::errors::HgError;
20 use crate::errors::{HgResultExt, IoResultExt};
25 use crate::errors::{HgResultExt, IoResultExt};
21 use crate::utils::files::get_bytes_from_os_str;
26 use crate::utils::files::get_bytes_from_os_str;
22 use format_bytes::{write_bytes, DisplayBytes};
27 use format_bytes::{write_bytes, DisplayBytes};
@@ -26,6 +31,14 b' use std::fmt;'
26 use std::path::{Path, PathBuf};
31 use std::path::{Path, PathBuf};
27 use std::str;
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 /// Holds the config values for the current repository
42 /// Holds the config values for the current repository
30 /// TODO update this docstring once we support more sources
43 /// TODO update this docstring once we support more sources
31 #[derive(Clone)]
44 #[derive(Clone)]
@@ -347,13 +360,32 b' impl Config {'
347 self.plain = plain;
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 fn get_parse<'config, T: 'config>(
379 fn get_parse<'config, T: 'config>(
351 &'config self,
380 &'config self,
352 section: &[u8],
381 section: &[u8],
353 item: &[u8],
382 item: &[u8],
354 expected_type: &'static str,
383 expected_type: &'static str,
355 parse: impl Fn(&'config [u8]) -> Option<T>,
384 parse: impl Fn(&'config [u8]) -> Option<T>,
356 ) -> Result<Option<T>, ConfigValueParseError> {
385 ) -> Result<Option<T>, HgError>
386 where
387 Option<T>: TryFrom<&'config DefaultConfigItem, Error = HgError>,
388 {
357 match self.get_inner(section, item) {
389 match self.get_inner(section, item) {
358 Some((layer, v)) => match parse(&v.bytes) {
390 Some((layer, v)) => match parse(&v.bytes) {
359 Some(b) => Ok(Some(b)),
391 Some(b) => Ok(Some(b)),
@@ -364,9 +396,15 b' impl Config {'
364 section: section.to_owned(),
396 section: section.to_owned(),
365 item: item.to_owned(),
397 item: item.to_owned(),
366 expected_type,
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 &self,
414 &self,
377 section: &[u8],
415 section: &[u8],
378 item: &[u8],
416 item: &[u8],
379 ) -> Result<Option<&str>, ConfigValueParseError> {
417 ) -> Result<Option<&str>, HgError> {
380 self.get_parse(section, item, "ASCII or UTF-8 string", |value| {
418 self.get_parse(section, item, "ASCII or UTF-8 string", |value| {
381 str::from_utf8(value).ok()
419 str::from_utf8(value).ok()
382 })
420 })
@@ -388,7 +426,7 b' impl Config {'
388 &self,
426 &self,
389 section: &[u8],
427 section: &[u8],
390 item: &[u8],
428 item: &[u8],
391 ) -> Result<Option<u32>, ConfigValueParseError> {
429 ) -> Result<Option<u32>, HgError> {
392 self.get_parse(section, item, "valid integer", |value| {
430 self.get_parse(section, item, "valid integer", |value| {
393 str::from_utf8(value).ok()?.parse().ok()
431 str::from_utf8(value).ok()?.parse().ok()
394 })
432 })
@@ -401,7 +439,7 b' impl Config {'
401 &self,
439 &self,
402 section: &[u8],
440 section: &[u8],
403 item: &[u8],
441 item: &[u8],
404 ) -> Result<Option<u64>, ConfigValueParseError> {
442 ) -> Result<Option<u64>, HgError> {
405 self.get_parse(section, item, "byte quantity", values::parse_byte_size)
443 self.get_parse(section, item, "byte quantity", values::parse_byte_size)
406 }
444 }
407
445
@@ -412,7 +450,7 b' impl Config {'
412 &self,
450 &self,
413 section: &[u8],
451 section: &[u8],
414 item: &[u8],
452 item: &[u8],
415 ) -> Result<Option<bool>, ConfigValueParseError> {
453 ) -> Result<Option<bool>, HgError> {
416 self.get_parse(section, item, "boolean", values::parse_bool)
454 self.get_parse(section, item, "boolean", values::parse_bool)
417 }
455 }
418
456
@@ -422,7 +460,7 b' impl Config {'
422 &self,
460 &self,
423 section: &[u8],
461 section: &[u8],
424 item: &[u8],
462 item: &[u8],
425 ) -> Result<bool, ConfigValueParseError> {
463 ) -> Result<bool, HgError> {
426 Ok(self.get_option(section, item)?.unwrap_or(false))
464 Ok(self.get_option(section, item)?.unwrap_or(false))
427 }
465 }
428
466
@@ -7,7 +7,8 b''
7
7
8 use crate::error::CommandError;
8 use crate::error::CommandError;
9 use crate::ui::{
9 use crate::ui::{
10 format_pattern_file_warning, print_narrow_sparse_warnings, Ui,
10 format_pattern_file_warning, print_narrow_sparse_warnings, relative_paths,
11 RelativePaths, Ui,
11 };
12 };
12 use crate::utils::path_utils::RelativizePaths;
13 use crate::utils::path_utils::RelativizePaths;
13 use clap::Arg;
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 .get_option(b"commands", b"status.relative")?
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 let output = DisplayStatusPaths {
380 let output = DisplayStatusPaths {
367 ui,
381 ui,
368 no_status,
382 no_status,
369 relativize: if relative_paths {
383 relativize: if relativize_paths {
370 Some(RelativizePaths::new(repo)?)
384 Some(RelativizePaths::new(repo)?)
371 } else {
385 } else {
372 None
386 None
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now