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