1 use libc; 2 use std::ffi::CString; 3 use std::marker; 4 use std::path::{Path, PathBuf}; 5 use std::ptr; 6 use std::str; 7 8 use crate::util::{self, Binding}; 9 use crate::{raw, Buf, ConfigLevel, Error, IntoCString}; 10 11 /// A structure representing a git configuration key/value store 12 pub struct Config { 13 raw: *mut raw::git_config, 14 } 15 16 /// A struct representing a certain entry owned by a `Config` instance. 17 /// 18 /// An entry has a name, a value, and a level it applies to. 19 pub struct ConfigEntry<'cfg> { 20 raw: *mut raw::git_config_entry, 21 _marker: marker::PhantomData<&'cfg Config>, 22 owned: bool, 23 } 24 25 /// An iterator over the `ConfigEntry` values of a `Config` structure. 26 pub struct ConfigEntries<'cfg> { 27 raw: *mut raw::git_config_iterator, 28 _marker: marker::PhantomData<&'cfg Config>, 29 } 30 31 impl Config { 32 /// Allocate a new configuration object 33 /// 34 /// This object is empty, so you have to add a file to it before you can do 35 /// anything with it. new() -> Result<Config, Error>36 pub fn new() -> Result<Config, Error> { 37 crate::init(); 38 let mut raw = ptr::null_mut(); 39 unsafe { 40 try_call!(raw::git_config_new(&mut raw)); 41 Ok(Binding::from_raw(raw)) 42 } 43 } 44 45 /// Create a new config instance containing a single on-disk file open(path: &Path) -> Result<Config, Error>46 pub fn open(path: &Path) -> Result<Config, Error> { 47 crate::init(); 48 let mut raw = ptr::null_mut(); 49 // Normal file path OK (does not need Windows conversion). 50 let path = path.into_c_string()?; 51 unsafe { 52 try_call!(raw::git_config_open_ondisk(&mut raw, path)); 53 Ok(Binding::from_raw(raw)) 54 } 55 } 56 57 /// Open the global, XDG and system configuration files 58 /// 59 /// Utility wrapper that finds the global, XDG and system configuration 60 /// files and opens them into a single prioritized config object that can 61 /// be used when accessing default config data outside a repository. open_default() -> Result<Config, Error>62 pub fn open_default() -> Result<Config, Error> { 63 crate::init(); 64 let mut raw = ptr::null_mut(); 65 unsafe { 66 try_call!(raw::git_config_open_default(&mut raw)); 67 Ok(Binding::from_raw(raw)) 68 } 69 } 70 71 /// Locate the path to the global configuration file 72 /// 73 /// The user or global configuration file is usually located in 74 /// `$HOME/.gitconfig`. 75 /// 76 /// This method will try to guess the full path to that file, if the file 77 /// exists. The returned path may be used on any method call to load 78 /// the global configuration file. 79 /// 80 /// This method will not guess the path to the xdg compatible config file 81 /// (`.config/git/config`). find_global() -> Result<PathBuf, Error>82 pub fn find_global() -> Result<PathBuf, Error> { 83 crate::init(); 84 let buf = Buf::new(); 85 unsafe { 86 try_call!(raw::git_config_find_global(buf.raw())); 87 } 88 Ok(util::bytes2path(&buf).to_path_buf()) 89 } 90 91 /// Locate the path to the system configuration file 92 /// 93 /// If /etc/gitconfig doesn't exist, it will look for %PROGRAMFILES% find_system() -> Result<PathBuf, Error>94 pub fn find_system() -> Result<PathBuf, Error> { 95 crate::init(); 96 let buf = Buf::new(); 97 unsafe { 98 try_call!(raw::git_config_find_system(buf.raw())); 99 } 100 Ok(util::bytes2path(&buf).to_path_buf()) 101 } 102 103 /// Locate the path to the global xdg compatible configuration file 104 /// 105 /// The xdg compatible configuration file is usually located in 106 /// `$HOME/.config/git/config`. find_xdg() -> Result<PathBuf, Error>107 pub fn find_xdg() -> Result<PathBuf, Error> { 108 crate::init(); 109 let buf = Buf::new(); 110 unsafe { 111 try_call!(raw::git_config_find_xdg(buf.raw())); 112 } 113 Ok(util::bytes2path(&buf).to_path_buf()) 114 } 115 116 /// Add an on-disk config file instance to an existing config 117 /// 118 /// The on-disk file pointed at by path will be opened and parsed; it's 119 /// expected to be a native Git config file following the default Git config 120 /// syntax (see man git-config). 121 /// 122 /// Further queries on this config object will access each of the config 123 /// file instances in order (instances with a higher priority level will be 124 /// accessed first). add_file(&mut self, path: &Path, level: ConfigLevel, force: bool) -> Result<(), Error>125 pub fn add_file(&mut self, path: &Path, level: ConfigLevel, force: bool) -> Result<(), Error> { 126 // Normal file path OK (does not need Windows conversion). 127 let path = path.into_c_string()?; 128 unsafe { 129 try_call!(raw::git_config_add_file_ondisk( 130 self.raw, 131 path, 132 level, 133 ptr::null(), 134 force 135 )); 136 Ok(()) 137 } 138 } 139 140 /// Delete a config variable from the config file with the highest level 141 /// (usually the local one). remove(&mut self, name: &str) -> Result<(), Error>142 pub fn remove(&mut self, name: &str) -> Result<(), Error> { 143 let name = CString::new(name)?; 144 unsafe { 145 try_call!(raw::git_config_delete_entry(self.raw, name)); 146 Ok(()) 147 } 148 } 149 150 /// Remove multivar config variables in the config file with the highest level (usually the 151 /// local one). remove_multivar(&mut self, name: &str, regexp: &str) -> Result<(), Error>152 pub fn remove_multivar(&mut self, name: &str, regexp: &str) -> Result<(), Error> { 153 let name = CString::new(name)?; 154 let regexp = CString::new(regexp)?; 155 unsafe { 156 try_call!(raw::git_config_delete_multivar(self.raw, name, regexp)); 157 } 158 Ok(()) 159 } 160 161 /// Get the value of a boolean config variable. 162 /// 163 /// All config files will be looked into, in the order of their defined 164 /// level. A higher level means a higher priority. The first occurrence of 165 /// the variable will be returned here. get_bool(&self, name: &str) -> Result<bool, Error>166 pub fn get_bool(&self, name: &str) -> Result<bool, Error> { 167 let mut out = 0 as libc::c_int; 168 let name = CString::new(name)?; 169 unsafe { 170 try_call!(raw::git_config_get_bool(&mut out, &*self.raw, name)); 171 } 172 Ok(out != 0) 173 } 174 175 /// Get the value of an integer config variable. 176 /// 177 /// All config files will be looked into, in the order of their defined 178 /// level. A higher level means a higher priority. The first occurrence of 179 /// the variable will be returned here. get_i32(&self, name: &str) -> Result<i32, Error>180 pub fn get_i32(&self, name: &str) -> Result<i32, Error> { 181 let mut out = 0i32; 182 let name = CString::new(name)?; 183 unsafe { 184 try_call!(raw::git_config_get_int32(&mut out, &*self.raw, name)); 185 } 186 Ok(out) 187 } 188 189 /// Get the value of an integer config variable. 190 /// 191 /// All config files will be looked into, in the order of their defined 192 /// level. A higher level means a higher priority. The first occurrence of 193 /// the variable will be returned here. get_i64(&self, name: &str) -> Result<i64, Error>194 pub fn get_i64(&self, name: &str) -> Result<i64, Error> { 195 let mut out = 0i64; 196 let name = CString::new(name)?; 197 unsafe { 198 try_call!(raw::git_config_get_int64(&mut out, &*self.raw, name)); 199 } 200 Ok(out) 201 } 202 203 /// Get the value of a string config variable. 204 /// 205 /// This is the same as `get_bytes` except that it may return `Err` if 206 /// the bytes are not valid utf-8. get_str(&self, name: &str) -> Result<&str, Error>207 pub fn get_str(&self, name: &str) -> Result<&str, Error> { 208 str::from_utf8(self.get_bytes(name)?) 209 .map_err(|_| Error::from_str("configuration value is not valid utf8")) 210 } 211 212 /// Get the value of a string config variable as a byte slice. 213 /// 214 /// This method will return an error if this `Config` is not a snapshot. get_bytes(&self, name: &str) -> Result<&[u8], Error>215 pub fn get_bytes(&self, name: &str) -> Result<&[u8], Error> { 216 let mut ret = ptr::null(); 217 let name = CString::new(name)?; 218 unsafe { 219 try_call!(raw::git_config_get_string(&mut ret, &*self.raw, name)); 220 Ok(crate::opt_bytes(self, ret).unwrap()) 221 } 222 } 223 224 /// Get the value of a string config variable as an owned string. 225 /// 226 /// An error will be returned if the config value is not valid utf-8. get_string(&self, name: &str) -> Result<String, Error>227 pub fn get_string(&self, name: &str) -> Result<String, Error> { 228 let ret = Buf::new(); 229 let name = CString::new(name)?; 230 unsafe { 231 try_call!(raw::git_config_get_string_buf(ret.raw(), self.raw, name)); 232 } 233 str::from_utf8(&ret) 234 .map(|s| s.to_string()) 235 .map_err(|_| Error::from_str("configuration value is not valid utf8")) 236 } 237 238 /// Get the value of a path config variable as an owned . get_path(&self, name: &str) -> Result<PathBuf, Error>239 pub fn get_path(&self, name: &str) -> Result<PathBuf, Error> { 240 let ret = Buf::new(); 241 let name = CString::new(name)?; 242 unsafe { 243 try_call!(raw::git_config_get_path(ret.raw(), self.raw, name)); 244 } 245 Ok(crate::util::bytes2path(&ret).to_path_buf()) 246 } 247 248 /// Get the ConfigEntry for a config variable. get_entry(&self, name: &str) -> Result<ConfigEntry<'_>, Error>249 pub fn get_entry(&self, name: &str) -> Result<ConfigEntry<'_>, Error> { 250 let mut ret = ptr::null_mut(); 251 let name = CString::new(name)?; 252 unsafe { 253 try_call!(raw::git_config_get_entry(&mut ret, self.raw, name)); 254 Ok(Binding::from_raw(ret)) 255 } 256 } 257 258 /// Iterate over all the config variables 259 /// 260 /// If `glob` is `Some`, then the iterator will only iterate over all 261 /// variables whose name matches the pattern. 262 /// 263 /// # Example 264 /// 265 /// ``` 266 /// # #![allow(unstable)] 267 /// use git2::Config; 268 /// 269 /// let cfg = Config::new().unwrap(); 270 /// 271 /// for entry in &cfg.entries(None).unwrap() { 272 /// let entry = entry.unwrap(); 273 /// println!("{} => {}", entry.name().unwrap(), entry.value().unwrap()); 274 /// } 275 /// ``` entries(&self, glob: Option<&str>) -> Result<ConfigEntries<'_>, Error>276 pub fn entries(&self, glob: Option<&str>) -> Result<ConfigEntries<'_>, Error> { 277 let mut ret = ptr::null_mut(); 278 unsafe { 279 match glob { 280 Some(s) => { 281 let s = CString::new(s)?; 282 try_call!(raw::git_config_iterator_glob_new(&mut ret, &*self.raw, s)); 283 } 284 None => { 285 try_call!(raw::git_config_iterator_new(&mut ret, &*self.raw)); 286 } 287 } 288 Ok(Binding::from_raw(ret)) 289 } 290 } 291 292 /// Iterate over the values of a multivar 293 /// 294 /// If `regexp` is `Some`, then the iterator will only iterate over all 295 /// values which match the pattern. multivar(&self, name: &str, regexp: Option<&str>) -> Result<ConfigEntries<'_>, Error>296 pub fn multivar(&self, name: &str, regexp: Option<&str>) -> Result<ConfigEntries<'_>, Error> { 297 let mut ret = ptr::null_mut(); 298 let name = CString::new(name)?; 299 let regexp = regexp.map(CString::new).transpose()?; 300 unsafe { 301 try_call!(raw::git_config_multivar_iterator_new( 302 &mut ret, &*self.raw, name, regexp 303 )); 304 Ok(Binding::from_raw(ret)) 305 } 306 } 307 308 /// Open the global/XDG configuration file according to git's rules 309 /// 310 /// Git allows you to store your global configuration at `$HOME/.config` or 311 /// `$XDG_CONFIG_HOME/git/config`. For backwards compatability, the XDG file 312 /// shouldn't be used unless the use has created it explicitly. With this 313 /// function you'll open the correct one to write to. open_global(&mut self) -> Result<Config, Error>314 pub fn open_global(&mut self) -> Result<Config, Error> { 315 let mut raw = ptr::null_mut(); 316 unsafe { 317 try_call!(raw::git_config_open_global(&mut raw, self.raw)); 318 Ok(Binding::from_raw(raw)) 319 } 320 } 321 322 /// Build a single-level focused config object from a multi-level one. 323 /// 324 /// The returned config object can be used to perform get/set/delete 325 /// operations on a single specific level. open_level(&self, level: ConfigLevel) -> Result<Config, Error>326 pub fn open_level(&self, level: ConfigLevel) -> Result<Config, Error> { 327 let mut raw = ptr::null_mut(); 328 unsafe { 329 try_call!(raw::git_config_open_level(&mut raw, &*self.raw, level)); 330 Ok(Binding::from_raw(raw)) 331 } 332 } 333 334 /// Set the value of a boolean config variable in the config file with the 335 /// highest level (usually the local one). set_bool(&mut self, name: &str, value: bool) -> Result<(), Error>336 pub fn set_bool(&mut self, name: &str, value: bool) -> Result<(), Error> { 337 let name = CString::new(name)?; 338 unsafe { 339 try_call!(raw::git_config_set_bool(self.raw, name, value)); 340 } 341 Ok(()) 342 } 343 344 /// Set the value of an integer config variable in the config file with the 345 /// highest level (usually the local one). set_i32(&mut self, name: &str, value: i32) -> Result<(), Error>346 pub fn set_i32(&mut self, name: &str, value: i32) -> Result<(), Error> { 347 let name = CString::new(name)?; 348 unsafe { 349 try_call!(raw::git_config_set_int32(self.raw, name, value)); 350 } 351 Ok(()) 352 } 353 354 /// Set the value of an integer config variable in the config file with the 355 /// highest level (usually the local one). set_i64(&mut self, name: &str, value: i64) -> Result<(), Error>356 pub fn set_i64(&mut self, name: &str, value: i64) -> Result<(), Error> { 357 let name = CString::new(name)?; 358 unsafe { 359 try_call!(raw::git_config_set_int64(self.raw, name, value)); 360 } 361 Ok(()) 362 } 363 364 /// Set the value of an multivar config variable in the config file with the 365 /// highest level (usually the local one). set_multivar(&mut self, name: &str, regexp: &str, value: &str) -> Result<(), Error>366 pub fn set_multivar(&mut self, name: &str, regexp: &str, value: &str) -> Result<(), Error> { 367 let name = CString::new(name)?; 368 let regexp = CString::new(regexp)?; 369 let value = CString::new(value)?; 370 unsafe { 371 try_call!(raw::git_config_set_multivar(self.raw, name, regexp, value)); 372 } 373 Ok(()) 374 } 375 376 /// Set the value of a string config variable in the config file with the 377 /// highest level (usually the local one). set_str(&mut self, name: &str, value: &str) -> Result<(), Error>378 pub fn set_str(&mut self, name: &str, value: &str) -> Result<(), Error> { 379 let name = CString::new(name)?; 380 let value = CString::new(value)?; 381 unsafe { 382 try_call!(raw::git_config_set_string(self.raw, name, value)); 383 } 384 Ok(()) 385 } 386 387 /// Create a snapshot of the configuration 388 /// 389 /// Create a snapshot of the current state of a configuration, which allows 390 /// you to look into a consistent view of the configuration for looking up 391 /// complex values (e.g. a remote, submodule). snapshot(&mut self) -> Result<Config, Error>392 pub fn snapshot(&mut self) -> Result<Config, Error> { 393 let mut ret = ptr::null_mut(); 394 unsafe { 395 try_call!(raw::git_config_snapshot(&mut ret, self.raw)); 396 Ok(Binding::from_raw(ret)) 397 } 398 } 399 400 /// Parse a string as a bool. 401 /// Interprets "true", "yes", "on", 1, or any non-zero number as true. 402 /// Interprets "false", "no", "off", 0, or an empty string as false. parse_bool<S: IntoCString>(s: S) -> Result<bool, Error>403 pub fn parse_bool<S: IntoCString>(s: S) -> Result<bool, Error> { 404 let s = s.into_c_string()?; 405 let mut out = 0; 406 crate::init(); 407 unsafe { 408 try_call!(raw::git_config_parse_bool(&mut out, s)); 409 } 410 Ok(out != 0) 411 } 412 413 /// Parse a string as an i32; handles suffixes like k, M, or G, and 414 /// multiplies by the appropriate power of 1024. parse_i32<S: IntoCString>(s: S) -> Result<i32, Error>415 pub fn parse_i32<S: IntoCString>(s: S) -> Result<i32, Error> { 416 let s = s.into_c_string()?; 417 let mut out = 0; 418 crate::init(); 419 unsafe { 420 try_call!(raw::git_config_parse_int32(&mut out, s)); 421 } 422 Ok(out) 423 } 424 425 /// Parse a string as an i64; handles suffixes like k, M, or G, and 426 /// multiplies by the appropriate power of 1024. parse_i64<S: IntoCString>(s: S) -> Result<i64, Error>427 pub fn parse_i64<S: IntoCString>(s: S) -> Result<i64, Error> { 428 let s = s.into_c_string()?; 429 let mut out = 0; 430 crate::init(); 431 unsafe { 432 try_call!(raw::git_config_parse_int64(&mut out, s)); 433 } 434 Ok(out) 435 } 436 } 437 438 impl Binding for Config { 439 type Raw = *mut raw::git_config; from_raw(raw: *mut raw::git_config) -> Config440 unsafe fn from_raw(raw: *mut raw::git_config) -> Config { 441 Config { raw: raw } 442 } raw(&self) -> *mut raw::git_config443 fn raw(&self) -> *mut raw::git_config { 444 self.raw 445 } 446 } 447 448 impl Drop for Config { drop(&mut self)449 fn drop(&mut self) { 450 unsafe { raw::git_config_free(self.raw) } 451 } 452 } 453 454 impl<'cfg> ConfigEntry<'cfg> { 455 /// Gets the name of this entry. 456 /// 457 /// May return `None` if the name is not valid utf-8 name(&self) -> Option<&str>458 pub fn name(&self) -> Option<&str> { 459 str::from_utf8(self.name_bytes()).ok() 460 } 461 462 /// Gets the name of this entry as a byte slice. name_bytes(&self) -> &[u8]463 pub fn name_bytes(&self) -> &[u8] { 464 unsafe { crate::opt_bytes(self, (*self.raw).name).unwrap() } 465 } 466 467 /// Gets the value of this entry. 468 /// 469 /// May return `None` if the value is not valid utf-8 470 /// 471 /// # Panics 472 /// 473 /// Panics when no value is defined. value(&self) -> Option<&str>474 pub fn value(&self) -> Option<&str> { 475 str::from_utf8(self.value_bytes()).ok() 476 } 477 478 /// Gets the value of this entry as a byte slice. 479 /// 480 /// # Panics 481 /// 482 /// Panics when no value is defined. value_bytes(&self) -> &[u8]483 pub fn value_bytes(&self) -> &[u8] { 484 unsafe { crate::opt_bytes(self, (*self.raw).value).unwrap() } 485 } 486 487 /// Returns `true` when a value is defined otherwise `false`. 488 /// 489 /// No value defined is a short-hand to represent a Boolean `true`. has_value(&self) -> bool490 pub fn has_value(&self) -> bool { 491 unsafe { !(*self.raw).value.is_null() } 492 } 493 494 /// Gets the configuration level of this entry. level(&self) -> ConfigLevel495 pub fn level(&self) -> ConfigLevel { 496 unsafe { ConfigLevel::from_raw((*self.raw).level) } 497 } 498 499 /// Depth of includes where this variable was found include_depth(&self) -> u32500 pub fn include_depth(&self) -> u32 { 501 unsafe { (*self.raw).include_depth as u32 } 502 } 503 } 504 505 impl<'cfg> Binding for ConfigEntry<'cfg> { 506 type Raw = *mut raw::git_config_entry; 507 from_raw(raw: *mut raw::git_config_entry) -> ConfigEntry<'cfg>508 unsafe fn from_raw(raw: *mut raw::git_config_entry) -> ConfigEntry<'cfg> { 509 ConfigEntry { 510 raw: raw, 511 _marker: marker::PhantomData, 512 owned: true, 513 } 514 } raw(&self) -> *mut raw::git_config_entry515 fn raw(&self) -> *mut raw::git_config_entry { 516 self.raw 517 } 518 } 519 520 impl<'cfg> Binding for ConfigEntries<'cfg> { 521 type Raw = *mut raw::git_config_iterator; 522 from_raw(raw: *mut raw::git_config_iterator) -> ConfigEntries<'cfg>523 unsafe fn from_raw(raw: *mut raw::git_config_iterator) -> ConfigEntries<'cfg> { 524 ConfigEntries { 525 raw: raw, 526 _marker: marker::PhantomData, 527 } 528 } raw(&self) -> *mut raw::git_config_iterator529 fn raw(&self) -> *mut raw::git_config_iterator { 530 self.raw 531 } 532 } 533 534 // entries are only valid until the iterator is freed, so this impl is for 535 // `&'b T` instead of `T` to have a lifetime to tie them to. 536 // 537 // It's also not implemented for `&'b mut T` so we can have multiple entries 538 // (ok). 539 impl<'cfg, 'b> Iterator for &'b ConfigEntries<'cfg> { 540 type Item = Result<ConfigEntry<'b>, Error>; next(&mut self) -> Option<Result<ConfigEntry<'b>, Error>>541 fn next(&mut self) -> Option<Result<ConfigEntry<'b>, Error>> { 542 let mut raw = ptr::null_mut(); 543 unsafe { 544 try_call_iter!(raw::git_config_next(&mut raw, self.raw)); 545 Some(Ok(ConfigEntry { 546 owned: false, 547 raw: raw, 548 _marker: marker::PhantomData, 549 })) 550 } 551 } 552 } 553 554 impl<'cfg> Drop for ConfigEntries<'cfg> { drop(&mut self)555 fn drop(&mut self) { 556 unsafe { raw::git_config_iterator_free(self.raw) } 557 } 558 } 559 560 impl<'cfg> Drop for ConfigEntry<'cfg> { drop(&mut self)561 fn drop(&mut self) { 562 if self.owned { 563 unsafe { raw::git_config_entry_free(self.raw) } 564 } 565 } 566 } 567 568 #[cfg(test)] 569 mod tests { 570 use std::fs::File; 571 use tempfile::TempDir; 572 573 use crate::Config; 574 575 #[test] smoke()576 fn smoke() { 577 let _cfg = Config::new().unwrap(); 578 let _ = Config::find_global(); 579 let _ = Config::find_system(); 580 let _ = Config::find_xdg(); 581 } 582 583 #[test] persisted()584 fn persisted() { 585 let td = TempDir::new().unwrap(); 586 let path = td.path().join("foo"); 587 File::create(&path).unwrap(); 588 589 let mut cfg = Config::open(&path).unwrap(); 590 assert!(cfg.get_bool("foo.bar").is_err()); 591 cfg.set_bool("foo.k1", true).unwrap(); 592 cfg.set_i32("foo.k2", 1).unwrap(); 593 cfg.set_i64("foo.k3", 2).unwrap(); 594 cfg.set_str("foo.k4", "bar").unwrap(); 595 cfg.snapshot().unwrap(); 596 drop(cfg); 597 598 let cfg = Config::open(&path).unwrap().snapshot().unwrap(); 599 assert_eq!(cfg.get_bool("foo.k1").unwrap(), true); 600 assert_eq!(cfg.get_i32("foo.k2").unwrap(), 1); 601 assert_eq!(cfg.get_i64("foo.k3").unwrap(), 2); 602 assert_eq!(cfg.get_str("foo.k4").unwrap(), "bar"); 603 604 for entry in &cfg.entries(None).unwrap() { 605 let entry = entry.unwrap(); 606 entry.name(); 607 entry.value(); 608 entry.level(); 609 } 610 } 611 612 #[test] multivar()613 fn multivar() { 614 let td = TempDir::new().unwrap(); 615 let path = td.path().join("foo"); 616 File::create(&path).unwrap(); 617 618 let mut cfg = Config::open(&path).unwrap(); 619 cfg.set_multivar("foo.bar", "^$", "baz").unwrap(); 620 cfg.set_multivar("foo.bar", "^$", "qux").unwrap(); 621 cfg.set_multivar("foo.bar", "^$", "quux").unwrap(); 622 cfg.set_multivar("foo.baz", "^$", "oki").unwrap(); 623 624 // `entries` filters by name 625 let mut entries: Vec<String> = cfg 626 .entries(Some("foo.bar")) 627 .unwrap() 628 .into_iter() 629 .map(|entry| entry.unwrap().value().unwrap().into()) 630 .collect(); 631 entries.sort(); 632 assert_eq!(entries, ["baz", "quux", "qux"]); 633 634 // which is the same as `multivar` without a regex 635 let mut multivals: Vec<String> = cfg 636 .multivar("foo.bar", None) 637 .unwrap() 638 .into_iter() 639 .map(|entry| entry.unwrap().value().unwrap().into()) 640 .collect(); 641 multivals.sort(); 642 assert_eq!(multivals, entries); 643 644 // yet _with_ a regex, `multivar` filters by value 645 let mut quxish: Vec<String> = cfg 646 .multivar("foo.bar", Some("qu.*x")) 647 .unwrap() 648 .into_iter() 649 .map(|entry| entry.unwrap().value().unwrap().into()) 650 .collect(); 651 quxish.sort(); 652 assert_eq!(quxish, ["quux", "qux"]); 653 654 cfg.remove_multivar("foo.bar", ".*").unwrap(); 655 656 assert_eq!(cfg.entries(Some("foo.bar")).unwrap().count(), 0); 657 assert_eq!(cfg.multivar("foo.bar", None).unwrap().count(), 0); 658 } 659 660 #[test] parse()661 fn parse() { 662 assert_eq!(Config::parse_bool("").unwrap(), false); 663 assert_eq!(Config::parse_bool("false").unwrap(), false); 664 assert_eq!(Config::parse_bool("no").unwrap(), false); 665 assert_eq!(Config::parse_bool("off").unwrap(), false); 666 assert_eq!(Config::parse_bool("0").unwrap(), false); 667 668 assert_eq!(Config::parse_bool("true").unwrap(), true); 669 assert_eq!(Config::parse_bool("yes").unwrap(), true); 670 assert_eq!(Config::parse_bool("on").unwrap(), true); 671 assert_eq!(Config::parse_bool("1").unwrap(), true); 672 assert_eq!(Config::parse_bool("42").unwrap(), true); 673 674 assert!(Config::parse_bool(" ").is_err()); 675 assert!(Config::parse_bool("some-string").is_err()); 676 assert!(Config::parse_bool("-").is_err()); 677 678 assert_eq!(Config::parse_i32("0").unwrap(), 0); 679 assert_eq!(Config::parse_i32("1").unwrap(), 1); 680 assert_eq!(Config::parse_i32("100").unwrap(), 100); 681 assert_eq!(Config::parse_i32("-1").unwrap(), -1); 682 assert_eq!(Config::parse_i32("-100").unwrap(), -100); 683 assert_eq!(Config::parse_i32("1k").unwrap(), 1024); 684 assert_eq!(Config::parse_i32("4k").unwrap(), 4096); 685 assert_eq!(Config::parse_i32("1M").unwrap(), 1048576); 686 assert_eq!(Config::parse_i32("1G").unwrap(), 1024 * 1024 * 1024); 687 688 assert_eq!(Config::parse_i64("0").unwrap(), 0); 689 assert_eq!(Config::parse_i64("1").unwrap(), 1); 690 assert_eq!(Config::parse_i64("100").unwrap(), 100); 691 assert_eq!(Config::parse_i64("-1").unwrap(), -1); 692 assert_eq!(Config::parse_i64("-100").unwrap(), -100); 693 assert_eq!(Config::parse_i64("1k").unwrap(), 1024); 694 assert_eq!(Config::parse_i64("4k").unwrap(), 4096); 695 assert_eq!(Config::parse_i64("1M").unwrap(), 1048576); 696 assert_eq!(Config::parse_i64("1G").unwrap(), 1024 * 1024 * 1024); 697 assert_eq!(Config::parse_i64("100G").unwrap(), 100 * 1024 * 1024 * 1024); 698 } 699 } 700