##// END OF EJS Templates
rust: use `peek_mut` from the standard lib now that it's stable...
Raphaël Gomès -
r50849:a1123772 default
parent child Browse files
Show More
@@ -1,657 +1,633 b''
1 1 // config.rs
2 2 //
3 3 // Copyright 2020
4 4 // Valentin Gatien-Baron,
5 5 // Raphaël Gomès <rgomes@octobus.net>
6 6 //
7 7 // This software may be used and distributed according to the terms of the
8 8 // GNU General Public License version 2 or any later version.
9 9
10 10 //! Mercurial config parsing and interfaces.
11 11
12 12 mod layer;
13 13 mod plain_info;
14 14 mod values;
15 15 pub use layer::{ConfigError, ConfigOrigin, ConfigParseError};
16 16 pub use plain_info::PlainInfo;
17 17
18 18 use self::layer::ConfigLayer;
19 19 use self::layer::ConfigValue;
20 20 use crate::errors::{HgResultExt, IoResultExt};
21 21 use crate::utils::files::get_bytes_from_os_str;
22 22 use format_bytes::{write_bytes, DisplayBytes};
23 23 use std::collections::HashSet;
24 24 use std::env;
25 25 use std::fmt;
26 26 use std::path::{Path, PathBuf};
27 27 use std::str;
28 28
29 29 /// Holds the config values for the current repository
30 30 /// TODO update this docstring once we support more sources
31 31 #[derive(Clone)]
32 32 pub struct Config {
33 33 layers: Vec<layer::ConfigLayer>,
34 34 plain: PlainInfo,
35 35 }
36 36
37 37 impl DisplayBytes for Config {
38 38 fn display_bytes(
39 39 &self,
40 40 out: &mut dyn std::io::Write,
41 41 ) -> std::io::Result<()> {
42 42 for (index, layer) in self.layers.iter().rev().enumerate() {
43 43 write_bytes!(
44 44 out,
45 45 b"==== Layer {} (trusted: {}) ====\n{}",
46 46 index,
47 47 if layer.trusted {
48 48 &b"yes"[..]
49 49 } else {
50 50 &b"no"[..]
51 51 },
52 52 layer
53 53 )?;
54 54 }
55 55 Ok(())
56 56 }
57 57 }
58 58
59 59 pub enum ConfigSource {
60 60 /// Absolute path to a config file
61 61 AbsPath(PathBuf),
62 62 /// Already parsed (from the CLI, env, Python resources, etc.)
63 63 Parsed(layer::ConfigLayer),
64 64 }
65 65
66 66 #[derive(Debug)]
67 67 pub struct ConfigValueParseError {
68 68 pub origin: ConfigOrigin,
69 69 pub line: Option<usize>,
70 70 pub section: Vec<u8>,
71 71 pub item: Vec<u8>,
72 72 pub value: Vec<u8>,
73 73 pub expected_type: &'static str,
74 74 }
75 75
76 76 impl fmt::Display for ConfigValueParseError {
77 77 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
78 78 // TODO: add origin and line number information, here and in
79 79 // corresponding python code
80 80 write!(
81 81 f,
82 82 "config error: {}.{} is not a {} ('{}')",
83 83 String::from_utf8_lossy(&self.section),
84 84 String::from_utf8_lossy(&self.item),
85 85 self.expected_type,
86 86 String::from_utf8_lossy(&self.value)
87 87 )
88 88 }
89 89 }
90 90
91 91 /// Returns true if the config item is disabled by PLAIN or PLAINEXCEPT
92 92 fn should_ignore(plain: &PlainInfo, section: &[u8], item: &[u8]) -> bool {
93 93 // duplication with [_applyconfig] in [ui.py],
94 94 if !plain.is_plain() {
95 95 return false;
96 96 }
97 97 if section == b"alias" {
98 98 return plain.plainalias();
99 99 }
100 100 if section == b"revsetalias" {
101 101 return plain.plainrevsetalias();
102 102 }
103 103 if section == b"templatealias" {
104 104 return plain.plaintemplatealias();
105 105 }
106 106 if section == b"ui" {
107 107 let to_delete: &[&[u8]] = &[
108 108 b"debug",
109 109 b"fallbackencoding",
110 110 b"quiet",
111 111 b"slash",
112 112 b"logtemplate",
113 113 b"message-output",
114 114 b"statuscopies",
115 115 b"style",
116 116 b"traceback",
117 117 b"verbose",
118 118 ];
119 119 return to_delete.contains(&item);
120 120 }
121 121 let sections_to_delete: &[&[u8]] =
122 122 &[b"defaults", b"commands", b"command-templates"];
123 123 sections_to_delete.contains(&section)
124 124 }
125 125
126 126 impl Config {
127 127 /// The configuration to use when printing configuration-loading errors
128 128 pub fn empty() -> Self {
129 129 Self {
130 130 layers: Vec::new(),
131 131 plain: PlainInfo::empty(),
132 132 }
133 133 }
134 134
135 135 /// Load system and user configuration from various files.
136 136 ///
137 137 /// This is also affected by some environment variables.
138 138 pub fn load_non_repo() -> Result<Self, ConfigError> {
139 139 let mut config = Self::empty();
140 140 let opt_rc_path = env::var_os("HGRCPATH");
141 141 // HGRCPATH replaces system config
142 142 if opt_rc_path.is_none() {
143 143 config.add_system_config()?
144 144 }
145 145
146 146 config.add_for_environment_variable("EDITOR", b"ui", b"editor");
147 147 config.add_for_environment_variable("VISUAL", b"ui", b"editor");
148 148 config.add_for_environment_variable("PAGER", b"pager", b"pager");
149 149
150 150 // These are set by `run-tests.py --rhg` to enable fallback for the
151 151 // entire test suite. Alternatives would be setting configuration
152 152 // through `$HGRCPATH` but some tests override that, or changing the
153 153 // `hg` shell alias to include `--config` but that disrupts tests that
154 154 // print command lines and check expected output.
155 155 config.add_for_environment_variable(
156 156 "RHG_ON_UNSUPPORTED",
157 157 b"rhg",
158 158 b"on-unsupported",
159 159 );
160 160 config.add_for_environment_variable(
161 161 "RHG_FALLBACK_EXECUTABLE",
162 162 b"rhg",
163 163 b"fallback-executable",
164 164 );
165 165
166 166 // HGRCPATH replaces user config
167 167 if opt_rc_path.is_none() {
168 168 config.add_user_config()?
169 169 }
170 170 if let Some(rc_path) = &opt_rc_path {
171 171 for path in env::split_paths(rc_path) {
172 172 if !path.as_os_str().is_empty() {
173 173 if path.is_dir() {
174 174 config.add_trusted_dir(&path)?
175 175 } else {
176 176 config.add_trusted_file(&path)?
177 177 }
178 178 }
179 179 }
180 180 }
181 181 Ok(config)
182 182 }
183 183
184 184 pub fn load_cli_args(
185 185 &mut self,
186 186 cli_config_args: impl IntoIterator<Item = impl AsRef<[u8]>>,
187 187 color_arg: Option<Vec<u8>>,
188 188 ) -> Result<(), ConfigError> {
189 189 if let Some(layer) = ConfigLayer::parse_cli_args(cli_config_args)? {
190 190 self.layers.push(layer)
191 191 }
192 192 if let Some(arg) = color_arg {
193 193 let mut layer = ConfigLayer::new(ConfigOrigin::CommandLineColor);
194 194 layer.add(b"ui"[..].into(), b"color"[..].into(), arg, None);
195 195 self.layers.push(layer)
196 196 }
197 197 Ok(())
198 198 }
199 199
200 200 fn add_trusted_dir(&mut self, path: &Path) -> Result<(), ConfigError> {
201 201 if let Some(entries) = std::fs::read_dir(path)
202 202 .when_reading_file(path)
203 203 .io_not_found_as_none()?
204 204 {
205 205 let mut file_paths = entries
206 206 .map(|result| {
207 207 result.when_reading_file(path).map(|entry| entry.path())
208 208 })
209 209 .collect::<Result<Vec<_>, _>>()?;
210 210 file_paths.sort();
211 211 for file_path in &file_paths {
212 212 if file_path.extension() == Some(std::ffi::OsStr::new("rc")) {
213 213 self.add_trusted_file(file_path)?
214 214 }
215 215 }
216 216 }
217 217 Ok(())
218 218 }
219 219
220 220 fn add_trusted_file(&mut self, path: &Path) -> Result<(), ConfigError> {
221 221 if let Some(data) = std::fs::read(path)
222 222 .when_reading_file(path)
223 223 .io_not_found_as_none()?
224 224 {
225 225 self.layers.extend(ConfigLayer::parse(path, &data)?)
226 226 }
227 227 Ok(())
228 228 }
229 229
230 230 fn add_for_environment_variable(
231 231 &mut self,
232 232 var: &str,
233 233 section: &[u8],
234 234 key: &[u8],
235 235 ) {
236 236 if let Some(value) = env::var_os(var) {
237 237 let origin = layer::ConfigOrigin::Environment(var.into());
238 238 let mut layer = ConfigLayer::new(origin);
239 239 layer.add(
240 240 section.to_owned(),
241 241 key.to_owned(),
242 242 get_bytes_from_os_str(value),
243 243 None,
244 244 );
245 245 self.layers.push(layer)
246 246 }
247 247 }
248 248
249 249 #[cfg(unix)] // TODO: other platforms
250 250 fn add_system_config(&mut self) -> Result<(), ConfigError> {
251 251 let mut add_for_prefix = |prefix: &Path| -> Result<(), ConfigError> {
252 252 let etc = prefix.join("etc").join("mercurial");
253 253 self.add_trusted_file(&etc.join("hgrc"))?;
254 254 self.add_trusted_dir(&etc.join("hgrc.d"))
255 255 };
256 256 let root = Path::new("/");
257 257 // TODO: use `std::env::args_os().next().unwrap()` a.k.a. argv[0]
258 258 // instead? TODO: can this be a relative path?
259 259 let hg = crate::utils::current_exe()?;
260 260 // TODO: this order (per-installation then per-system) matches
261 261 // `systemrcpath()` in `mercurial/scmposix.py`, but
262 262 // `mercurial/helptext/config.txt` suggests it should be reversed
263 263 if let Some(installation_prefix) = hg.parent().and_then(Path::parent) {
264 264 if installation_prefix != root {
265 265 add_for_prefix(installation_prefix)?
266 266 }
267 267 }
268 268 add_for_prefix(root)?;
269 269 Ok(())
270 270 }
271 271
272 272 #[cfg(unix)] // TODO: other plateforms
273 273 fn add_user_config(&mut self) -> Result<(), ConfigError> {
274 274 let opt_home = home::home_dir();
275 275 if let Some(home) = &opt_home {
276 276 self.add_trusted_file(&home.join(".hgrc"))?
277 277 }
278 278 let darwin = cfg!(any(target_os = "macos", target_os = "ios"));
279 279 if !darwin {
280 280 if let Some(config_home) = env::var_os("XDG_CONFIG_HOME")
281 281 .map(PathBuf::from)
282 282 .or_else(|| opt_home.map(|home| home.join(".config")))
283 283 {
284 284 self.add_trusted_file(&config_home.join("hg").join("hgrc"))?
285 285 }
286 286 }
287 287 Ok(())
288 288 }
289 289
290 290 /// Loads in order, which means that the precedence is the same
291 291 /// as the order of `sources`.
292 292 pub fn load_from_explicit_sources(
293 293 sources: Vec<ConfigSource>,
294 294 ) -> Result<Self, ConfigError> {
295 295 let mut layers = vec![];
296 296
297 297 for source in sources.into_iter() {
298 298 match source {
299 299 ConfigSource::Parsed(c) => layers.push(c),
300 300 ConfigSource::AbsPath(c) => {
301 301 // TODO check if it should be trusted
302 302 // mercurial/ui.py:427
303 303 let data = match std::fs::read(&c) {
304 304 Err(_) => continue, // same as the python code
305 305 Ok(data) => data,
306 306 };
307 307 layers.extend(ConfigLayer::parse(&c, &data)?)
308 308 }
309 309 }
310 310 }
311 311
312 312 Ok(Config {
313 313 layers,
314 314 plain: PlainInfo::empty(),
315 315 })
316 316 }
317 317
318 318 /// Loads the per-repository config into a new `Config` which is combined
319 319 /// with `self`.
320 320 pub(crate) fn combine_with_repo(
321 321 &self,
322 322 repo_config_files: &[PathBuf],
323 323 ) -> Result<Self, ConfigError> {
324 324 let (cli_layers, other_layers) = self
325 325 .layers
326 326 .iter()
327 327 .cloned()
328 328 .partition(ConfigLayer::is_from_command_line);
329 329
330 330 let mut repo_config = Self {
331 331 layers: other_layers,
332 332 plain: PlainInfo::empty(),
333 333 };
334 334 for path in repo_config_files {
335 335 // TODO: check if this file should be trusted:
336 336 // `mercurial/ui.py:427`
337 337 repo_config.add_trusted_file(path)?;
338 338 }
339 339 repo_config.layers.extend(cli_layers);
340 340 Ok(repo_config)
341 341 }
342 342
343 343 pub fn apply_plain(&mut self, plain: PlainInfo) {
344 344 self.plain = plain;
345 345 }
346 346
347 347 fn get_parse<'config, T: 'config>(
348 348 &'config self,
349 349 section: &[u8],
350 350 item: &[u8],
351 351 expected_type: &'static str,
352 352 parse: impl Fn(&'config [u8]) -> Option<T>,
353 353 ) -> Result<Option<T>, ConfigValueParseError> {
354 354 match self.get_inner(section, item) {
355 355 Some((layer, v)) => match parse(&v.bytes) {
356 356 Some(b) => Ok(Some(b)),
357 357 None => Err(ConfigValueParseError {
358 358 origin: layer.origin.to_owned(),
359 359 line: v.line,
360 360 value: v.bytes.to_owned(),
361 361 section: section.to_owned(),
362 362 item: item.to_owned(),
363 363 expected_type,
364 364 }),
365 365 },
366 366 None => Ok(None),
367 367 }
368 368 }
369 369
370 370 /// Returns an `Err` if the first value found is not a valid UTF-8 string.
371 371 /// Otherwise, returns an `Ok(value)` if found, or `None`.
372 372 pub fn get_str(
373 373 &self,
374 374 section: &[u8],
375 375 item: &[u8],
376 376 ) -> Result<Option<&str>, ConfigValueParseError> {
377 377 self.get_parse(section, item, "ASCII or UTF-8 string", |value| {
378 378 str::from_utf8(value).ok()
379 379 })
380 380 }
381 381
382 382 /// Returns an `Err` if the first value found is not a valid unsigned
383 383 /// integer. Otherwise, returns an `Ok(value)` if found, or `None`.
384 384 pub fn get_u32(
385 385 &self,
386 386 section: &[u8],
387 387 item: &[u8],
388 388 ) -> Result<Option<u32>, ConfigValueParseError> {
389 389 self.get_parse(section, item, "valid integer", |value| {
390 390 str::from_utf8(value).ok()?.parse().ok()
391 391 })
392 392 }
393 393
394 394 /// Returns an `Err` if the first value found is not a valid file size
395 395 /// value such as `30` (default unit is bytes), `7 MB`, or `42.5 kb`.
396 396 /// Otherwise, returns an `Ok(value_in_bytes)` if found, or `None`.
397 397 pub fn get_byte_size(
398 398 &self,
399 399 section: &[u8],
400 400 item: &[u8],
401 401 ) -> Result<Option<u64>, ConfigValueParseError> {
402 402 self.get_parse(section, item, "byte quantity", values::parse_byte_size)
403 403 }
404 404
405 405 /// Returns an `Err` if the first value found is not a valid boolean.
406 406 /// Otherwise, returns an `Ok(option)`, where `option` is the boolean if
407 407 /// found, or `None`.
408 408 pub fn get_option(
409 409 &self,
410 410 section: &[u8],
411 411 item: &[u8],
412 412 ) -> Result<Option<bool>, ConfigValueParseError> {
413 413 self.get_parse(section, item, "boolean", values::parse_bool)
414 414 }
415 415
416 416 /// Returns the corresponding boolean in the config. Returns `Ok(false)`
417 417 /// if the value is not found, an `Err` if it's not a valid boolean.
418 418 pub fn get_bool(
419 419 &self,
420 420 section: &[u8],
421 421 item: &[u8],
422 422 ) -> Result<bool, ConfigValueParseError> {
423 423 Ok(self.get_option(section, item)?.unwrap_or(false))
424 424 }
425 425
426 426 /// Returns `true` if the extension is enabled, `false` otherwise
427 427 pub fn is_extension_enabled(&self, extension: &[u8]) -> bool {
428 428 let value = self.get(b"extensions", extension);
429 429 match value {
430 430 Some(c) => !c.starts_with(b"!"),
431 431 None => false,
432 432 }
433 433 }
434 434
435 435 /// If there is an `item` value in `section`, parse and return a list of
436 436 /// byte strings.
437 437 pub fn get_list(
438 438 &self,
439 439 section: &[u8],
440 440 item: &[u8],
441 441 ) -> Option<Vec<Vec<u8>>> {
442 442 self.get(section, item).map(values::parse_list)
443 443 }
444 444
445 445 /// Returns the raw value bytes of the first one found, or `None`.
446 446 pub fn get(&self, section: &[u8], item: &[u8]) -> Option<&[u8]> {
447 447 self.get_inner(section, item)
448 448 .map(|(_, value)| value.bytes.as_ref())
449 449 }
450 450
451 451 /// Returns the raw value bytes of the first one found, or `None`.
452 452 pub fn get_with_origin(
453 453 &self,
454 454 section: &[u8],
455 455 item: &[u8],
456 456 ) -> Option<(&[u8], &ConfigOrigin)> {
457 457 self.get_inner(section, item)
458 458 .map(|(layer, value)| (value.bytes.as_ref(), &layer.origin))
459 459 }
460 460
461 461 /// Returns the layer and the value of the first one found, or `None`.
462 462 fn get_inner(
463 463 &self,
464 464 section: &[u8],
465 465 item: &[u8],
466 466 ) -> Option<(&ConfigLayer, &ConfigValue)> {
467 467 // Filter out the config items that are hidden by [PLAIN].
468 468 // This differs from python hg where we delete them from the config.
469 469 let should_ignore = should_ignore(&self.plain, section, item);
470 470 for layer in self.layers.iter().rev() {
471 471 if !layer.trusted {
472 472 continue;
473 473 }
474 474 //The [PLAIN] config should not affect the defaults.
475 475 //
476 476 // However, PLAIN should also affect the "tweaked" defaults (unless
477 477 // "tweakdefault" is part of "HGPLAINEXCEPT").
478 478 //
479 479 // In practice the tweak-default layer is only added when it is
480 480 // relevant, so we can safely always take it into
481 481 // account here.
482 482 if should_ignore && !(layer.origin == ConfigOrigin::Tweakdefaults)
483 483 {
484 484 continue;
485 485 }
486 486 if let Some(v) = layer.get(section, item) {
487 487 return Some((layer, v));
488 488 }
489 489 }
490 490 None
491 491 }
492 492
493 493 /// Return all keys defined for the given section
494 494 pub fn get_section_keys(&self, section: &[u8]) -> HashSet<&[u8]> {
495 495 self.layers
496 496 .iter()
497 497 .flat_map(|layer| layer.iter_keys(section))
498 498 .collect()
499 499 }
500 500
501 501 /// Returns whether any key is defined in the given section
502 502 pub fn has_non_empty_section(&self, section: &[u8]) -> bool {
503 503 self.layers
504 504 .iter()
505 505 .any(|layer| layer.has_non_empty_section(section))
506 506 }
507 507
508 508 /// Yields (key, value) pairs for everything in the given section
509 509 pub fn iter_section<'a>(
510 510 &'a self,
511 511 section: &'a [u8],
512 512 ) -> impl Iterator<Item = (&[u8], &[u8])> + 'a {
513 // TODO: Use `Iterator`’s `.peekable()` when its `peek_mut` is
514 // available:
515 // https://doc.rust-lang.org/nightly/std/iter/struct.Peekable.html#method.peek_mut
516 struct Peekable<I: Iterator> {
517 iter: I,
518 /// Remember a peeked value, even if it was None.
519 peeked: Option<Option<I::Item>>,
520 }
521
522 impl<I: Iterator> Peekable<I> {
523 fn new(iter: I) -> Self {
524 Self { iter, peeked: None }
525 }
526
527 fn next(&mut self) {
528 self.peeked = None
529 }
530
531 fn peek_mut(&mut self) -> Option<&mut I::Item> {
532 let iter = &mut self.iter;
533 self.peeked.get_or_insert_with(|| iter.next()).as_mut()
534 }
535 }
536
537 513 // Deduplicate keys redefined in multiple layers
538 514 let mut keys_already_seen = HashSet::new();
539 515 let mut key_is_new =
540 516 move |&(key, _value): &(&'a [u8], &'a [u8])| -> bool {
541 517 keys_already_seen.insert(key)
542 518 };
543 519 // This is similar to `flat_map` + `filter_map`, except with a single
544 520 // closure that owns `key_is_new` (and therefore the
545 521 // `keys_already_seen` set):
546 let mut layer_iters = Peekable::new(
547 self.layers
548 .iter()
549 .rev()
550 .map(move |layer| layer.iter_section(section)),
551 );
522 let mut layer_iters = self
523 .layers
524 .iter()
525 .rev()
526 .map(move |layer| layer.iter_section(section))
527 .peekable();
552 528 std::iter::from_fn(move || loop {
553 529 if let Some(pair) = layer_iters.peek_mut()?.find(&mut key_is_new) {
554 530 return Some(pair);
555 531 } else {
556 532 layer_iters.next();
557 533 }
558 534 })
559 535 }
560 536
561 537 /// Get raw values bytes from all layers (even untrusted ones) in order
562 538 /// of precedence.
563 539 #[cfg(test)]
564 540 fn get_all(&self, section: &[u8], item: &[u8]) -> Vec<&[u8]> {
565 541 let mut res = vec![];
566 542 for layer in self.layers.iter().rev() {
567 543 if let Some(v) = layer.get(section, item) {
568 544 res.push(v.bytes.as_ref());
569 545 }
570 546 }
571 547 res
572 548 }
573 549
574 550 // a config layer that's introduced by ui.tweakdefaults
575 551 fn tweakdefaults_layer() -> ConfigLayer {
576 552 let mut layer = ConfigLayer::new(ConfigOrigin::Tweakdefaults);
577 553
578 554 let mut add = |section: &[u8], item: &[u8], value: &[u8]| {
579 555 layer.add(
580 556 section[..].into(),
581 557 item[..].into(),
582 558 value[..].into(),
583 559 None,
584 560 );
585 561 };
586 562 // duplication of [tweakrc] from [ui.py]
587 563 add(b"ui", b"rollback", b"False");
588 564 add(b"ui", b"statuscopies", b"yes");
589 565 add(b"ui", b"interface", b"curses");
590 566 add(b"ui", b"relative-paths", b"yes");
591 567 add(b"commands", b"grep.all-files", b"True");
592 568 add(b"commands", b"update.check", b"noconflict");
593 569 add(b"commands", b"status.verbose", b"True");
594 570 add(b"commands", b"resolve.explicit-re-merge", b"True");
595 571 add(b"git", b"git", b"1");
596 572 add(b"git", b"showfunc", b"1");
597 573 add(b"git", b"word-diff", b"1");
598 574 layer
599 575 }
600 576
601 577 // introduce the tweaked defaults as implied by ui.tweakdefaults
602 578 pub fn tweakdefaults(&mut self) {
603 579 self.layers.insert(0, Config::tweakdefaults_layer());
604 580 }
605 581 }
606 582
607 583 #[cfg(test)]
608 584 mod tests {
609 585 use super::*;
610 586 use pretty_assertions::assert_eq;
611 587 use std::fs::File;
612 588 use std::io::Write;
613 589
614 590 #[test]
615 591 fn test_include_layer_ordering() {
616 592 let tmpdir = tempfile::tempdir().unwrap();
617 593 let tmpdir_path = tmpdir.path();
618 594 let mut included_file =
619 595 File::create(&tmpdir_path.join("included.rc")).unwrap();
620 596
621 597 included_file.write_all(b"[section]\nitem=value1").unwrap();
622 598 let base_config_path = tmpdir_path.join("base.rc");
623 599 let mut config_file = File::create(&base_config_path).unwrap();
624 600 let data =
625 601 b"[section]\nitem=value0\n%include included.rc\nitem=value2\n\
626 602 [section2]\ncount = 4\nsize = 1.5 KB\nnot-count = 1.5\nnot-size = 1 ub";
627 603 config_file.write_all(data).unwrap();
628 604
629 605 let sources = vec![ConfigSource::AbsPath(base_config_path)];
630 606 let config = Config::load_from_explicit_sources(sources)
631 607 .expect("expected valid config");
632 608
633 609 let (_, value) = config.get_inner(b"section", b"item").unwrap();
634 610 assert_eq!(
635 611 value,
636 612 &ConfigValue {
637 613 bytes: b"value2".to_vec(),
638 614 line: Some(4)
639 615 }
640 616 );
641 617
642 618 let value = config.get(b"section", b"item").unwrap();
643 619 assert_eq!(value, b"value2",);
644 620 assert_eq!(
645 621 config.get_all(b"section", b"item"),
646 622 [b"value2", b"value1", b"value0"]
647 623 );
648 624
649 625 assert_eq!(config.get_u32(b"section2", b"count").unwrap(), Some(4));
650 626 assert_eq!(
651 627 config.get_byte_size(b"section2", b"size").unwrap(),
652 628 Some(1024 + 512)
653 629 );
654 630 assert!(config.get_u32(b"section2", b"not-count").is_err());
655 631 assert!(config.get_byte_size(b"section2", b"not-size").is_err());
656 632 }
657 633 }
General Comments 0
You need to be logged in to leave comments. Login now