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