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). 152 /// 153 /// The regular expression is applied case-sensitively on the value. remove_multivar(&mut self, name: &str, regexp: &str) -> Result<(), Error>154 pub fn remove_multivar(&mut self, name: &str, regexp: &str) -> Result<(), Error> { 155 let name = CString::new(name)?; 156 let regexp = CString::new(regexp)?; 157 unsafe { 158 try_call!(raw::git_config_delete_multivar(self.raw, name, regexp)); 159 } 160 Ok(()) 161 } 162 163 /// Get the value of a boolean config variable. 164 /// 165 /// All config files will be looked into, in the order of their defined 166 /// level. A higher level means a higher priority. The first occurrence of 167 /// the variable will be returned here. get_bool(&self, name: &str) -> Result<bool, Error>168 pub fn get_bool(&self, name: &str) -> Result<bool, Error> { 169 let mut out = 0 as libc::c_int; 170 let name = CString::new(name)?; 171 unsafe { 172 try_call!(raw::git_config_get_bool(&mut out, &*self.raw, name)); 173 } 174 Ok(out != 0) 175 } 176 177 /// Get the value of an integer config variable. 178 /// 179 /// All config files will be looked into, in the order of their defined 180 /// level. A higher level means a higher priority. The first occurrence of 181 /// the variable will be returned here. get_i32(&self, name: &str) -> Result<i32, Error>182 pub fn get_i32(&self, name: &str) -> Result<i32, Error> { 183 let mut out = 0i32; 184 let name = CString::new(name)?; 185 unsafe { 186 try_call!(raw::git_config_get_int32(&mut out, &*self.raw, name)); 187 } 188 Ok(out) 189 } 190 191 /// Get the value of an integer config variable. 192 /// 193 /// All config files will be looked into, in the order of their defined 194 /// level. A higher level means a higher priority. The first occurrence of 195 /// the variable will be returned here. get_i64(&self, name: &str) -> Result<i64, Error>196 pub fn get_i64(&self, name: &str) -> Result<i64, Error> { 197 let mut out = 0i64; 198 let name = CString::new(name)?; 199 unsafe { 200 try_call!(raw::git_config_get_int64(&mut out, &*self.raw, name)); 201 } 202 Ok(out) 203 } 204 205 /// Get the value of a string config variable. 206 /// 207 /// This is the same as `get_bytes` except that it may return `Err` if 208 /// the bytes are not valid utf-8. 209 /// 210 /// This method will return an error if this `Config` is not a snapshot. get_str(&self, name: &str) -> Result<&str, Error>211 pub fn get_str(&self, name: &str) -> Result<&str, Error> { 212 str::from_utf8(self.get_bytes(name)?) 213 .map_err(|_| Error::from_str("configuration value is not valid utf8")) 214 } 215 216 /// Get the value of a string config variable as a byte slice. 217 /// 218 /// This method will return an error if this `Config` is not a snapshot. get_bytes(&self, name: &str) -> Result<&[u8], Error>219 pub fn get_bytes(&self, name: &str) -> Result<&[u8], Error> { 220 let mut ret = ptr::null(); 221 let name = CString::new(name)?; 222 unsafe { 223 try_call!(raw::git_config_get_string(&mut ret, &*self.raw, name)); 224 Ok(crate::opt_bytes(self, ret).unwrap()) 225 } 226 } 227 228 /// Get the value of a string config variable as an owned string. 229 /// 230 /// All config files will be looked into, in the order of their 231 /// defined level. A higher level means a higher priority. The 232 /// first occurrence of the variable will be returned here. 233 /// 234 /// An error will be returned if the config value is not valid utf-8. get_string(&self, name: &str) -> Result<String, Error>235 pub fn get_string(&self, name: &str) -> Result<String, Error> { 236 let ret = Buf::new(); 237 let name = CString::new(name)?; 238 unsafe { 239 try_call!(raw::git_config_get_string_buf(ret.raw(), self.raw, name)); 240 } 241 str::from_utf8(&ret) 242 .map(|s| s.to_string()) 243 .map_err(|_| Error::from_str("configuration value is not valid utf8")) 244 } 245 246 /// Get the value of a path config variable as an owned `PathBuf`. 247 /// 248 /// A leading '~' will be expanded to the global search path (which 249 /// defaults to the user's home directory but can be overridden via 250 /// [`raw::git_libgit2_opts`]. 251 /// 252 /// All config files will be looked into, in the order of their 253 /// defined level. A higher level means a higher priority. The 254 /// first occurrence of the variable will be returned here. get_path(&self, name: &str) -> Result<PathBuf, Error>255 pub fn get_path(&self, name: &str) -> Result<PathBuf, Error> { 256 let ret = Buf::new(); 257 let name = CString::new(name)?; 258 unsafe { 259 try_call!(raw::git_config_get_path(ret.raw(), self.raw, name)); 260 } 261 Ok(crate::util::bytes2path(&ret).to_path_buf()) 262 } 263 264 /// Get the ConfigEntry for a config variable. get_entry(&self, name: &str) -> Result<ConfigEntry<'_>, Error>265 pub fn get_entry(&self, name: &str) -> Result<ConfigEntry<'_>, Error> { 266 let mut ret = ptr::null_mut(); 267 let name = CString::new(name)?; 268 unsafe { 269 try_call!(raw::git_config_get_entry(&mut ret, self.raw, name)); 270 Ok(Binding::from_raw(ret)) 271 } 272 } 273 274 /// Iterate over all the config variables 275 /// 276 /// If `glob` is `Some`, then the iterator will only iterate over all 277 /// variables whose name matches the pattern. 278 /// 279 /// The regular expression is applied case-sensitively on the normalized form of 280 /// the variable name: the section and variable parts are lower-cased. The 281 /// subsection is left unchanged. 282 /// 283 /// # Example 284 /// 285 /// ``` 286 /// # #![allow(unstable)] 287 /// use git2::Config; 288 /// 289 /// let cfg = Config::new().unwrap(); 290 /// 291 /// for entry in &cfg.entries(None).unwrap() { 292 /// let entry = entry.unwrap(); 293 /// println!("{} => {}", entry.name().unwrap(), entry.value().unwrap()); 294 /// } 295 /// ``` entries(&self, glob: Option<&str>) -> Result<ConfigEntries<'_>, Error>296 pub fn entries(&self, glob: Option<&str>) -> Result<ConfigEntries<'_>, Error> { 297 let mut ret = ptr::null_mut(); 298 unsafe { 299 match glob { 300 Some(s) => { 301 let s = CString::new(s)?; 302 try_call!(raw::git_config_iterator_glob_new(&mut ret, &*self.raw, s)); 303 } 304 None => { 305 try_call!(raw::git_config_iterator_new(&mut ret, &*self.raw)); 306 } 307 } 308 Ok(Binding::from_raw(ret)) 309 } 310 } 311 312 /// Iterate over the values of a multivar 313 /// 314 /// If `regexp` is `Some`, then the iterator will only iterate over all 315 /// values which match the pattern. 316 /// 317 /// The regular expression is applied case-sensitively on the normalized form of 318 /// the variable name: the section and variable parts are lower-cased. The 319 /// subsection is left unchanged. multivar(&self, name: &str, regexp: Option<&str>) -> Result<ConfigEntries<'_>, Error>320 pub fn multivar(&self, name: &str, regexp: Option<&str>) -> Result<ConfigEntries<'_>, Error> { 321 let mut ret = ptr::null_mut(); 322 let name = CString::new(name)?; 323 let regexp = regexp.map(CString::new).transpose()?; 324 unsafe { 325 try_call!(raw::git_config_multivar_iterator_new( 326 &mut ret, &*self.raw, name, regexp 327 )); 328 Ok(Binding::from_raw(ret)) 329 } 330 } 331 332 /// Open the global/XDG configuration file according to git's rules 333 /// 334 /// Git allows you to store your global configuration at `$HOME/.config` or 335 /// `$XDG_CONFIG_HOME/git/config`. For backwards compatability, the XDG file 336 /// shouldn't be used unless the use has created it explicitly. With this 337 /// function you'll open the correct one to write to. open_global(&mut self) -> Result<Config, Error>338 pub fn open_global(&mut self) -> Result<Config, Error> { 339 let mut raw = ptr::null_mut(); 340 unsafe { 341 try_call!(raw::git_config_open_global(&mut raw, self.raw)); 342 Ok(Binding::from_raw(raw)) 343 } 344 } 345 346 /// Build a single-level focused config object from a multi-level one. 347 /// 348 /// The returned config object can be used to perform get/set/delete 349 /// operations on a single specific level. open_level(&self, level: ConfigLevel) -> Result<Config, Error>350 pub fn open_level(&self, level: ConfigLevel) -> Result<Config, Error> { 351 let mut raw = ptr::null_mut(); 352 unsafe { 353 try_call!(raw::git_config_open_level(&mut raw, &*self.raw, level)); 354 Ok(Binding::from_raw(raw)) 355 } 356 } 357 358 /// Set the value of a boolean config variable in the config file with the 359 /// highest level (usually the local one). set_bool(&mut self, name: &str, value: bool) -> Result<(), Error>360 pub fn set_bool(&mut self, name: &str, value: bool) -> Result<(), Error> { 361 let name = CString::new(name)?; 362 unsafe { 363 try_call!(raw::git_config_set_bool(self.raw, name, value)); 364 } 365 Ok(()) 366 } 367 368 /// Set the value of an integer config variable in the config file with the 369 /// highest level (usually the local one). set_i32(&mut self, name: &str, value: i32) -> Result<(), Error>370 pub fn set_i32(&mut self, name: &str, value: i32) -> Result<(), Error> { 371 let name = CString::new(name)?; 372 unsafe { 373 try_call!(raw::git_config_set_int32(self.raw, name, value)); 374 } 375 Ok(()) 376 } 377 378 /// Set the value of an integer config variable in the config file with the 379 /// highest level (usually the local one). set_i64(&mut self, name: &str, value: i64) -> Result<(), Error>380 pub fn set_i64(&mut self, name: &str, value: i64) -> Result<(), Error> { 381 let name = CString::new(name)?; 382 unsafe { 383 try_call!(raw::git_config_set_int64(self.raw, name, value)); 384 } 385 Ok(()) 386 } 387 388 /// Set the value of an multivar config variable in the config file with the 389 /// highest level (usually the local one). 390 /// 391 /// The regular expression is applied case-sensitively on the value. set_multivar(&mut self, name: &str, regexp: &str, value: &str) -> Result<(), Error>392 pub fn set_multivar(&mut self, name: &str, regexp: &str, value: &str) -> Result<(), Error> { 393 let name = CString::new(name)?; 394 let regexp = CString::new(regexp)?; 395 let value = CString::new(value)?; 396 unsafe { 397 try_call!(raw::git_config_set_multivar(self.raw, name, regexp, value)); 398 } 399 Ok(()) 400 } 401 402 /// Set the value of a string config variable in the config file with the 403 /// highest level (usually the local one). set_str(&mut self, name: &str, value: &str) -> Result<(), Error>404 pub fn set_str(&mut self, name: &str, value: &str) -> Result<(), Error> { 405 let name = CString::new(name)?; 406 let value = CString::new(value)?; 407 unsafe { 408 try_call!(raw::git_config_set_string(self.raw, name, value)); 409 } 410 Ok(()) 411 } 412 413 /// Create a snapshot of the configuration 414 /// 415 /// Create a snapshot of the current state of a configuration, which allows 416 /// you to look into a consistent view of the configuration for looking up 417 /// complex values (e.g. a remote, submodule). snapshot(&mut self) -> Result<Config, Error>418 pub fn snapshot(&mut self) -> Result<Config, Error> { 419 let mut ret = ptr::null_mut(); 420 unsafe { 421 try_call!(raw::git_config_snapshot(&mut ret, self.raw)); 422 Ok(Binding::from_raw(ret)) 423 } 424 } 425 426 /// Parse a string as a bool. 427 /// 428 /// Interprets "true", "yes", "on", 1, or any non-zero number as true. 429 /// Interprets "false", "no", "off", 0, or an empty string as false. parse_bool<S: IntoCString>(s: S) -> Result<bool, Error>430 pub fn parse_bool<S: IntoCString>(s: S) -> Result<bool, Error> { 431 let s = s.into_c_string()?; 432 let mut out = 0; 433 crate::init(); 434 unsafe { 435 try_call!(raw::git_config_parse_bool(&mut out, s)); 436 } 437 Ok(out != 0) 438 } 439 440 /// Parse a string as an i32; handles suffixes like k, M, or G, and 441 /// multiplies by the appropriate power of 1024. parse_i32<S: IntoCString>(s: S) -> Result<i32, Error>442 pub fn parse_i32<S: IntoCString>(s: S) -> Result<i32, Error> { 443 let s = s.into_c_string()?; 444 let mut out = 0; 445 crate::init(); 446 unsafe { 447 try_call!(raw::git_config_parse_int32(&mut out, s)); 448 } 449 Ok(out) 450 } 451 452 /// Parse a string as an i64; handles suffixes like k, M, or G, and 453 /// multiplies by the appropriate power of 1024. parse_i64<S: IntoCString>(s: S) -> Result<i64, Error>454 pub fn parse_i64<S: IntoCString>(s: S) -> Result<i64, Error> { 455 let s = s.into_c_string()?; 456 let mut out = 0; 457 crate::init(); 458 unsafe { 459 try_call!(raw::git_config_parse_int64(&mut out, s)); 460 } 461 Ok(out) 462 } 463 } 464 465 impl Binding for Config { 466 type Raw = *mut raw::git_config; from_raw(raw: *mut raw::git_config) -> Config467 unsafe fn from_raw(raw: *mut raw::git_config) -> Config { 468 Config { raw } 469 } raw(&self) -> *mut raw::git_config470 fn raw(&self) -> *mut raw::git_config { 471 self.raw 472 } 473 } 474 475 impl Drop for Config { drop(&mut self)476 fn drop(&mut self) { 477 unsafe { raw::git_config_free(self.raw) } 478 } 479 } 480 481 impl<'cfg> ConfigEntry<'cfg> { 482 /// Gets the name of this entry. 483 /// 484 /// May return `None` if the name is not valid utf-8 name(&self) -> Option<&str>485 pub fn name(&self) -> Option<&str> { 486 str::from_utf8(self.name_bytes()).ok() 487 } 488 489 /// Gets the name of this entry as a byte slice. name_bytes(&self) -> &[u8]490 pub fn name_bytes(&self) -> &[u8] { 491 unsafe { crate::opt_bytes(self, (*self.raw).name).unwrap() } 492 } 493 494 /// Gets the value of this entry. 495 /// 496 /// May return `None` if the value is not valid utf-8 497 /// 498 /// # Panics 499 /// 500 /// Panics when no value is defined. value(&self) -> Option<&str>501 pub fn value(&self) -> Option<&str> { 502 str::from_utf8(self.value_bytes()).ok() 503 } 504 505 /// Gets the value of this entry as a byte slice. 506 /// 507 /// # Panics 508 /// 509 /// Panics when no value is defined. value_bytes(&self) -> &[u8]510 pub fn value_bytes(&self) -> &[u8] { 511 unsafe { crate::opt_bytes(self, (*self.raw).value).unwrap() } 512 } 513 514 /// Returns `true` when a value is defined otherwise `false`. 515 /// 516 /// No value defined is a short-hand to represent a Boolean `true`. has_value(&self) -> bool517 pub fn has_value(&self) -> bool { 518 unsafe { !(*self.raw).value.is_null() } 519 } 520 521 /// Gets the configuration level of this entry. level(&self) -> ConfigLevel522 pub fn level(&self) -> ConfigLevel { 523 unsafe { ConfigLevel::from_raw((*self.raw).level) } 524 } 525 526 /// Depth of includes where this variable was found include_depth(&self) -> u32527 pub fn include_depth(&self) -> u32 { 528 unsafe { (*self.raw).include_depth as u32 } 529 } 530 } 531 532 impl<'cfg> Binding for ConfigEntry<'cfg> { 533 type Raw = *mut raw::git_config_entry; 534 from_raw(raw: *mut raw::git_config_entry) -> ConfigEntry<'cfg>535 unsafe fn from_raw(raw: *mut raw::git_config_entry) -> ConfigEntry<'cfg> { 536 ConfigEntry { 537 raw, 538 _marker: marker::PhantomData, 539 owned: true, 540 } 541 } raw(&self) -> *mut raw::git_config_entry542 fn raw(&self) -> *mut raw::git_config_entry { 543 self.raw 544 } 545 } 546 547 impl<'cfg> Binding for ConfigEntries<'cfg> { 548 type Raw = *mut raw::git_config_iterator; 549 from_raw(raw: *mut raw::git_config_iterator) -> ConfigEntries<'cfg>550 unsafe fn from_raw(raw: *mut raw::git_config_iterator) -> ConfigEntries<'cfg> { 551 ConfigEntries { 552 raw, 553 _marker: marker::PhantomData, 554 } 555 } raw(&self) -> *mut raw::git_config_iterator556 fn raw(&self) -> *mut raw::git_config_iterator { 557 self.raw 558 } 559 } 560 561 // entries are only valid until the iterator is freed, so this impl is for 562 // `&'b T` instead of `T` to have a lifetime to tie them to. 563 // 564 // It's also not implemented for `&'b mut T` so we can have multiple entries 565 // (ok). 566 impl<'cfg, 'b> Iterator for &'b ConfigEntries<'cfg> { 567 type Item = Result<ConfigEntry<'b>, Error>; next(&mut self) -> Option<Result<ConfigEntry<'b>, Error>>568 fn next(&mut self) -> Option<Result<ConfigEntry<'b>, Error>> { 569 let mut raw = ptr::null_mut(); 570 unsafe { 571 try_call_iter!(raw::git_config_next(&mut raw, self.raw)); 572 Some(Ok(ConfigEntry { 573 owned: false, 574 raw, 575 _marker: marker::PhantomData, 576 })) 577 } 578 } 579 } 580 581 impl<'cfg> Drop for ConfigEntries<'cfg> { drop(&mut self)582 fn drop(&mut self) { 583 unsafe { raw::git_config_iterator_free(self.raw) } 584 } 585 } 586 587 impl<'cfg> Drop for ConfigEntry<'cfg> { drop(&mut self)588 fn drop(&mut self) { 589 if self.owned { 590 unsafe { raw::git_config_entry_free(self.raw) } 591 } 592 } 593 } 594 595 #[cfg(test)] 596 mod tests { 597 use std::fs::File; 598 use tempfile::TempDir; 599 600 use crate::Config; 601 602 #[test] smoke()603 fn smoke() { 604 let _cfg = Config::new().unwrap(); 605 let _ = Config::find_global(); 606 let _ = Config::find_system(); 607 let _ = Config::find_xdg(); 608 } 609 610 #[test] persisted()611 fn persisted() { 612 let td = TempDir::new().unwrap(); 613 let path = td.path().join("foo"); 614 File::create(&path).unwrap(); 615 616 let mut cfg = Config::open(&path).unwrap(); 617 assert!(cfg.get_bool("foo.bar").is_err()); 618 cfg.set_bool("foo.k1", true).unwrap(); 619 cfg.set_i32("foo.k2", 1).unwrap(); 620 cfg.set_i64("foo.k3", 2).unwrap(); 621 cfg.set_str("foo.k4", "bar").unwrap(); 622 cfg.snapshot().unwrap(); 623 drop(cfg); 624 625 let cfg = Config::open(&path).unwrap().snapshot().unwrap(); 626 assert_eq!(cfg.get_bool("foo.k1").unwrap(), true); 627 assert_eq!(cfg.get_i32("foo.k2").unwrap(), 1); 628 assert_eq!(cfg.get_i64("foo.k3").unwrap(), 2); 629 assert_eq!(cfg.get_str("foo.k4").unwrap(), "bar"); 630 631 for entry in &cfg.entries(None).unwrap() { 632 let entry = entry.unwrap(); 633 entry.name(); 634 entry.value(); 635 entry.level(); 636 } 637 } 638 639 #[test] multivar()640 fn multivar() { 641 let td = TempDir::new().unwrap(); 642 let path = td.path().join("foo"); 643 File::create(&path).unwrap(); 644 645 let mut cfg = Config::open(&path).unwrap(); 646 cfg.set_multivar("foo.bar", "^$", "baz").unwrap(); 647 cfg.set_multivar("foo.bar", "^$", "qux").unwrap(); 648 cfg.set_multivar("foo.bar", "^$", "quux").unwrap(); 649 cfg.set_multivar("foo.baz", "^$", "oki").unwrap(); 650 651 // `entries` filters by name 652 let mut entries: Vec<String> = cfg 653 .entries(Some("foo.bar")) 654 .unwrap() 655 .into_iter() 656 .map(|entry| entry.unwrap().value().unwrap().into()) 657 .collect(); 658 entries.sort(); 659 assert_eq!(entries, ["baz", "quux", "qux"]); 660 661 // which is the same as `multivar` without a regex 662 let mut multivals: Vec<String> = cfg 663 .multivar("foo.bar", None) 664 .unwrap() 665 .into_iter() 666 .map(|entry| entry.unwrap().value().unwrap().into()) 667 .collect(); 668 multivals.sort(); 669 assert_eq!(multivals, entries); 670 671 // yet _with_ a regex, `multivar` filters by value 672 let mut quxish: Vec<String> = cfg 673 .multivar("foo.bar", Some("qu.*x")) 674 .unwrap() 675 .into_iter() 676 .map(|entry| entry.unwrap().value().unwrap().into()) 677 .collect(); 678 quxish.sort(); 679 assert_eq!(quxish, ["quux", "qux"]); 680 681 cfg.remove_multivar("foo.bar", ".*").unwrap(); 682 683 assert_eq!(cfg.entries(Some("foo.bar")).unwrap().count(), 0); 684 assert_eq!(cfg.multivar("foo.bar", None).unwrap().count(), 0); 685 } 686 687 #[test] parse()688 fn parse() { 689 assert_eq!(Config::parse_bool("").unwrap(), false); 690 assert_eq!(Config::parse_bool("false").unwrap(), false); 691 assert_eq!(Config::parse_bool("no").unwrap(), false); 692 assert_eq!(Config::parse_bool("off").unwrap(), false); 693 assert_eq!(Config::parse_bool("0").unwrap(), false); 694 695 assert_eq!(Config::parse_bool("true").unwrap(), true); 696 assert_eq!(Config::parse_bool("yes").unwrap(), true); 697 assert_eq!(Config::parse_bool("on").unwrap(), true); 698 assert_eq!(Config::parse_bool("1").unwrap(), true); 699 assert_eq!(Config::parse_bool("42").unwrap(), true); 700 701 assert!(Config::parse_bool(" ").is_err()); 702 assert!(Config::parse_bool("some-string").is_err()); 703 assert!(Config::parse_bool("-").is_err()); 704 705 assert_eq!(Config::parse_i32("0").unwrap(), 0); 706 assert_eq!(Config::parse_i32("1").unwrap(), 1); 707 assert_eq!(Config::parse_i32("100").unwrap(), 100); 708 assert_eq!(Config::parse_i32("-1").unwrap(), -1); 709 assert_eq!(Config::parse_i32("-100").unwrap(), -100); 710 assert_eq!(Config::parse_i32("1k").unwrap(), 1024); 711 assert_eq!(Config::parse_i32("4k").unwrap(), 4096); 712 assert_eq!(Config::parse_i32("1M").unwrap(), 1048576); 713 assert_eq!(Config::parse_i32("1G").unwrap(), 1024 * 1024 * 1024); 714 715 assert_eq!(Config::parse_i64("0").unwrap(), 0); 716 assert_eq!(Config::parse_i64("1").unwrap(), 1); 717 assert_eq!(Config::parse_i64("100").unwrap(), 100); 718 assert_eq!(Config::parse_i64("-1").unwrap(), -1); 719 assert_eq!(Config::parse_i64("-100").unwrap(), -100); 720 assert_eq!(Config::parse_i64("1k").unwrap(), 1024); 721 assert_eq!(Config::parse_i64("4k").unwrap(), 4096); 722 assert_eq!(Config::parse_i64("1M").unwrap(), 1048576); 723 assert_eq!(Config::parse_i64("1G").unwrap(), 1024 * 1024 * 1024); 724 assert_eq!(Config::parse_i64("100G").unwrap(), 100 * 1024 * 1024 * 1024); 725 } 726 } 727