git2/
config.rs

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