summaryrefslogtreecommitdiffstats
path: root/vendor/git2/src/config.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/git2/src/config.rs')
-rw-r--r--vendor/git2/src/config.rs777
1 files changed, 777 insertions, 0 deletions
diff --git a/vendor/git2/src/config.rs b/vendor/git2/src/config.rs
new file mode 100644
index 0000000..ae5c4ff
--- /dev/null
+++ b/vendor/git2/src/config.rs
@@ -0,0 +1,777 @@
+use libc;
+use std::ffi::CString;
+use std::marker;
+use std::path::{Path, PathBuf};
+use std::ptr;
+use std::str;
+
+use crate::util::{self, Binding};
+use crate::{raw, Buf, ConfigLevel, Error, IntoCString};
+
+/// A structure representing a git configuration key/value store
+pub struct Config {
+ raw: *mut raw::git_config,
+}
+
+/// A struct representing a certain entry owned by a `Config` instance.
+///
+/// An entry has a name, a value, and a level it applies to.
+pub struct ConfigEntry<'cfg> {
+ raw: *mut raw::git_config_entry,
+ _marker: marker::PhantomData<&'cfg Config>,
+ owned: bool,
+}
+
+/// An iterator over the `ConfigEntry` values of a `Config` structure.
+///
+/// Due to lifetime restrictions, `ConfigEntries` does not implement the
+/// standard [`Iterator`] trait. It provides a [`next`] function which only
+/// allows access to one entry at a time. [`for_each`] is available as a
+/// convenience function.
+///
+/// [`next`]: ConfigEntries::next
+/// [`for_each`]: ConfigEntries::for_each
+///
+/// # Example
+///
+/// ```
+/// // Example of how to collect all entries.
+/// use git2::Config;
+///
+/// let config = Config::new()?;
+/// let iter = config.entries(None)?;
+/// let mut entries = Vec::new();
+/// iter
+/// .for_each(|entry| {
+/// let name = entry.name().unwrap().to_string();
+/// let value = entry.value().unwrap_or("").to_string();
+/// entries.push((name, value))
+/// })?;
+/// for entry in &entries {
+/// println!("{} = {}", entry.0, entry.1);
+/// }
+/// # Ok::<(), git2::Error>(())
+///
+/// ```
+pub struct ConfigEntries<'cfg> {
+ raw: *mut raw::git_config_iterator,
+ current: Option<ConfigEntry<'cfg>>,
+ _marker: marker::PhantomData<&'cfg Config>,
+}
+
+impl Config {
+ /// Allocate a new configuration object
+ ///
+ /// This object is empty, so you have to add a file to it before you can do
+ /// anything with it.
+ pub fn new() -> Result<Config, Error> {
+ crate::init();
+ let mut raw = ptr::null_mut();
+ unsafe {
+ try_call!(raw::git_config_new(&mut raw));
+ Ok(Binding::from_raw(raw))
+ }
+ }
+
+ /// Create a new config instance containing a single on-disk file
+ pub fn open(path: &Path) -> Result<Config, Error> {
+ crate::init();
+ let mut raw = ptr::null_mut();
+ // Normal file path OK (does not need Windows conversion).
+ let path = path.into_c_string()?;
+ unsafe {
+ try_call!(raw::git_config_open_ondisk(&mut raw, path));
+ Ok(Binding::from_raw(raw))
+ }
+ }
+
+ /// Open the global, XDG and system configuration files
+ ///
+ /// Utility wrapper that finds the global, XDG and system configuration
+ /// files and opens them into a single prioritized config object that can
+ /// be used when accessing default config data outside a repository.
+ pub fn open_default() -> Result<Config, Error> {
+ crate::init();
+ let mut raw = ptr::null_mut();
+ unsafe {
+ try_call!(raw::git_config_open_default(&mut raw));
+ Ok(Binding::from_raw(raw))
+ }
+ }
+
+ /// Locate the path to the global configuration file
+ ///
+ /// The user or global configuration file is usually located in
+ /// `$HOME/.gitconfig`.
+ ///
+ /// This method will try to guess the full path to that file, if the file
+ /// exists. The returned path may be used on any method call to load
+ /// the global configuration file.
+ ///
+ /// This method will not guess the path to the XDG compatible config file
+ /// (`.config/git/config`).
+ pub fn find_global() -> Result<PathBuf, Error> {
+ crate::init();
+ let buf = Buf::new();
+ unsafe {
+ try_call!(raw::git_config_find_global(buf.raw()));
+ }
+ Ok(util::bytes2path(&buf).to_path_buf())
+ }
+
+ /// Locate the path to the system configuration file
+ ///
+ /// If /etc/gitconfig doesn't exist, it will look for `%PROGRAMFILES%`
+ pub fn find_system() -> Result<PathBuf, Error> {
+ crate::init();
+ let buf = Buf::new();
+ unsafe {
+ try_call!(raw::git_config_find_system(buf.raw()));
+ }
+ Ok(util::bytes2path(&buf).to_path_buf())
+ }
+
+ /// Locate the path to the global XDG compatible configuration file
+ ///
+ /// The XDG compatible configuration file is usually located in
+ /// `$HOME/.config/git/config`.
+ pub fn find_xdg() -> Result<PathBuf, Error> {
+ crate::init();
+ let buf = Buf::new();
+ unsafe {
+ try_call!(raw::git_config_find_xdg(buf.raw()));
+ }
+ Ok(util::bytes2path(&buf).to_path_buf())
+ }
+
+ /// Add an on-disk config file instance to an existing config
+ ///
+ /// The on-disk file pointed at by path will be opened and parsed; it's
+ /// expected to be a native Git config file following the default Git config
+ /// syntax (see man git-config).
+ ///
+ /// Further queries on this config object will access each of the config
+ /// file instances in order (instances with a higher priority level will be
+ /// accessed first).
+ pub fn add_file(&mut self, path: &Path, level: ConfigLevel, force: bool) -> Result<(), Error> {
+ // Normal file path OK (does not need Windows conversion).
+ let path = path.into_c_string()?;
+ unsafe {
+ try_call!(raw::git_config_add_file_ondisk(
+ self.raw,
+ path,
+ level,
+ ptr::null(),
+ force
+ ));
+ Ok(())
+ }
+ }
+
+ /// Delete a config variable from the config file with the highest level
+ /// (usually the local one).
+ pub fn remove(&mut self, name: &str) -> Result<(), Error> {
+ let name = CString::new(name)?;
+ unsafe {
+ try_call!(raw::git_config_delete_entry(self.raw, name));
+ Ok(())
+ }
+ }
+
+ /// Remove multivar config variables in the config file with the highest level (usually the
+ /// local one).
+ ///
+ /// The regular expression is applied case-sensitively on the value.
+ pub fn remove_multivar(&mut self, name: &str, regexp: &str) -> Result<(), Error> {
+ let name = CString::new(name)?;
+ let regexp = CString::new(regexp)?;
+ unsafe {
+ try_call!(raw::git_config_delete_multivar(self.raw, name, regexp));
+ }
+ Ok(())
+ }
+
+ /// Get the value of a boolean config variable.
+ ///
+ /// All config files will be looked into, in the order of their defined
+ /// level. A higher level means a higher priority. The first occurrence of
+ /// the variable will be returned here.
+ pub fn get_bool(&self, name: &str) -> Result<bool, Error> {
+ let mut out = 0 as libc::c_int;
+ let name = CString::new(name)?;
+ unsafe {
+ try_call!(raw::git_config_get_bool(&mut out, &*self.raw, name));
+ }
+ Ok(out != 0)
+ }
+
+ /// Get the value of an integer config variable.
+ ///
+ /// All config files will be looked into, in the order of their defined
+ /// level. A higher level means a higher priority. The first occurrence of
+ /// the variable will be returned here.
+ pub fn get_i32(&self, name: &str) -> Result<i32, Error> {
+ let mut out = 0i32;
+ let name = CString::new(name)?;
+ unsafe {
+ try_call!(raw::git_config_get_int32(&mut out, &*self.raw, name));
+ }
+ Ok(out)
+ }
+
+ /// Get the value of an integer config variable.
+ ///
+ /// All config files will be looked into, in the order of their defined
+ /// level. A higher level means a higher priority. The first occurrence of
+ /// the variable will be returned here.
+ pub fn get_i64(&self, name: &str) -> Result<i64, Error> {
+ let mut out = 0i64;
+ let name = CString::new(name)?;
+ unsafe {
+ try_call!(raw::git_config_get_int64(&mut out, &*self.raw, name));
+ }
+ Ok(out)
+ }
+
+ /// Get the value of a string config variable.
+ ///
+ /// This is the same as `get_bytes` except that it may return `Err` if
+ /// the bytes are not valid utf-8.
+ ///
+ /// This method will return an error if this `Config` is not a snapshot.
+ pub fn get_str(&self, name: &str) -> Result<&str, Error> {
+ str::from_utf8(self.get_bytes(name)?)
+ .map_err(|_| Error::from_str("configuration value is not valid utf8"))
+ }
+
+ /// Get the value of a string config variable as a byte slice.
+ ///
+ /// This method will return an error if this `Config` is not a snapshot.
+ pub fn get_bytes(&self, name: &str) -> Result<&[u8], Error> {
+ let mut ret = ptr::null();
+ let name = CString::new(name)?;
+ unsafe {
+ try_call!(raw::git_config_get_string(&mut ret, &*self.raw, name));
+ Ok(crate::opt_bytes(self, ret).unwrap())
+ }
+ }
+
+ /// Get the value of a string config variable as an owned string.
+ ///
+ /// All config files will be looked into, in the order of their
+ /// defined level. A higher level means a higher priority. The
+ /// first occurrence of the variable will be returned here.
+ ///
+ /// An error will be returned if the config value is not valid utf-8.
+ pub fn get_string(&self, name: &str) -> Result<String, Error> {
+ let ret = Buf::new();
+ let name = CString::new(name)?;
+ unsafe {
+ try_call!(raw::git_config_get_string_buf(ret.raw(), self.raw, name));
+ }
+ str::from_utf8(&ret)
+ .map(|s| s.to_string())
+ .map_err(|_| Error::from_str("configuration value is not valid utf8"))
+ }
+
+ /// Get the value of a path config variable as an owned `PathBuf`.
+ ///
+ /// A leading '~' will be expanded to the global search path (which
+ /// defaults to the user's home directory but can be overridden via
+ /// [`raw::git_libgit2_opts`].
+ ///
+ /// All config files will be looked into, in the order of their
+ /// defined level. A higher level means a higher priority. The
+ /// first occurrence of the variable will be returned here.
+ pub fn get_path(&self, name: &str) -> Result<PathBuf, Error> {
+ let ret = Buf::new();
+ let name = CString::new(name)?;
+ unsafe {
+ try_call!(raw::git_config_get_path(ret.raw(), self.raw, name));
+ }
+ Ok(crate::util::bytes2path(&ret).to_path_buf())
+ }
+
+ /// Get the ConfigEntry for a config variable.
+ pub fn get_entry(&self, name: &str) -> Result<ConfigEntry<'_>, Error> {
+ let mut ret = ptr::null_mut();
+ let name = CString::new(name)?;
+ unsafe {
+ try_call!(raw::git_config_get_entry(&mut ret, self.raw, name));
+ Ok(Binding::from_raw(ret))
+ }
+ }
+
+ /// Iterate over all the config variables
+ ///
+ /// If `glob` is `Some`, then the iterator will only iterate over all
+ /// variables whose name matches the pattern.
+ ///
+ /// The regular expression is applied case-sensitively on the normalized form of
+ /// the variable name: the section and variable parts are lower-cased. The
+ /// subsection is left unchanged.
+ ///
+ /// Due to lifetime restrictions, the returned value does not implement
+ /// the standard [`Iterator`] trait. See [`ConfigEntries`] for more.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use git2::Config;
+ ///
+ /// let cfg = Config::new().unwrap();
+ ///
+ /// let mut entries = cfg.entries(None).unwrap();
+ /// while let Some(entry) = entries.next() {
+ /// let entry = entry.unwrap();
+ /// println!("{} => {}", entry.name().unwrap(), entry.value().unwrap());
+ /// }
+ /// ```
+ pub fn entries(&self, glob: Option<&str>) -> Result<ConfigEntries<'_>, Error> {
+ let mut ret = ptr::null_mut();
+ unsafe {
+ match glob {
+ Some(s) => {
+ let s = CString::new(s)?;
+ try_call!(raw::git_config_iterator_glob_new(&mut ret, &*self.raw, s));
+ }
+ None => {
+ try_call!(raw::git_config_iterator_new(&mut ret, &*self.raw));
+ }
+ }
+ Ok(Binding::from_raw(ret))
+ }
+ }
+
+ /// Iterate over the values of a multivar
+ ///
+ /// If `regexp` is `Some`, then the iterator will only iterate over all
+ /// values which match the pattern.
+ ///
+ /// The regular expression is applied case-sensitively on the normalized form of
+ /// the variable name: the section and variable parts are lower-cased. The
+ /// subsection is left unchanged.
+ ///
+ /// Due to lifetime restrictions, the returned value does not implement
+ /// the standard [`Iterator`] trait. See [`ConfigEntries`] for more.
+ pub fn multivar(&self, name: &str, regexp: Option<&str>) -> Result<ConfigEntries<'_>, Error> {
+ let mut ret = ptr::null_mut();
+ let name = CString::new(name)?;
+ let regexp = regexp.map(CString::new).transpose()?;
+ unsafe {
+ try_call!(raw::git_config_multivar_iterator_new(
+ &mut ret, &*self.raw, name, regexp
+ ));
+ Ok(Binding::from_raw(ret))
+ }
+ }
+
+ /// Open the global/XDG configuration file according to git's rules
+ ///
+ /// Git allows you to store your global configuration at `$HOME/.config` or
+ /// `$XDG_CONFIG_HOME/git/config`. For backwards compatibility, the XDG file
+ /// shouldn't be used unless the use has created it explicitly. With this
+ /// function you'll open the correct one to write to.
+ pub fn open_global(&mut self) -> Result<Config, Error> {
+ let mut raw = ptr::null_mut();
+ unsafe {
+ try_call!(raw::git_config_open_global(&mut raw, self.raw));
+ Ok(Binding::from_raw(raw))
+ }
+ }
+
+ /// Build a single-level focused config object from a multi-level one.
+ ///
+ /// The returned config object can be used to perform get/set/delete
+ /// operations on a single specific level.
+ pub fn open_level(&self, level: ConfigLevel) -> Result<Config, Error> {
+ let mut raw = ptr::null_mut();
+ unsafe {
+ try_call!(raw::git_config_open_level(&mut raw, &*self.raw, level));
+ Ok(Binding::from_raw(raw))
+ }
+ }
+
+ /// Set the value of a boolean config variable in the config file with the
+ /// highest level (usually the local one).
+ pub fn set_bool(&mut self, name: &str, value: bool) -> Result<(), Error> {
+ let name = CString::new(name)?;
+ unsafe {
+ try_call!(raw::git_config_set_bool(self.raw, name, value));
+ }
+ Ok(())
+ }
+
+ /// Set the value of an integer config variable in the config file with the
+ /// highest level (usually the local one).
+ pub fn set_i32(&mut self, name: &str, value: i32) -> Result<(), Error> {
+ let name = CString::new(name)?;
+ unsafe {
+ try_call!(raw::git_config_set_int32(self.raw, name, value));
+ }
+ Ok(())
+ }
+
+ /// Set the value of an integer config variable in the config file with the
+ /// highest level (usually the local one).
+ pub fn set_i64(&mut self, name: &str, value: i64) -> Result<(), Error> {
+ let name = CString::new(name)?;
+ unsafe {
+ try_call!(raw::git_config_set_int64(self.raw, name, value));
+ }
+ Ok(())
+ }
+
+ /// Set the value of an multivar config variable in the config file with the
+ /// highest level (usually the local one).
+ ///
+ /// The regular expression is applied case-sensitively on the value.
+ pub fn set_multivar(&mut self, name: &str, regexp: &str, value: &str) -> Result<(), Error> {
+ let name = CString::new(name)?;
+ let regexp = CString::new(regexp)?;
+ let value = CString::new(value)?;
+ unsafe {
+ try_call!(raw::git_config_set_multivar(self.raw, name, regexp, value));
+ }
+ Ok(())
+ }
+
+ /// Set the value of a string config variable in the config file with the
+ /// highest level (usually the local one).
+ pub fn set_str(&mut self, name: &str, value: &str) -> Result<(), Error> {
+ let name = CString::new(name)?;
+ let value = CString::new(value)?;
+ unsafe {
+ try_call!(raw::git_config_set_string(self.raw, name, value));
+ }
+ Ok(())
+ }
+
+ /// Create a snapshot of the configuration
+ ///
+ /// Create a snapshot of the current state of a configuration, which allows
+ /// you to look into a consistent view of the configuration for looking up
+ /// complex values (e.g. a remote, submodule).
+ pub fn snapshot(&mut self) -> Result<Config, Error> {
+ let mut ret = ptr::null_mut();
+ unsafe {
+ try_call!(raw::git_config_snapshot(&mut ret, self.raw));
+ Ok(Binding::from_raw(ret))
+ }
+ }
+
+ /// Parse a string as a bool.
+ ///
+ /// Interprets "true", "yes", "on", 1, or any non-zero number as true.
+ /// Interprets "false", "no", "off", 0, or an empty string as false.
+ pub fn parse_bool<S: IntoCString>(s: S) -> Result<bool, Error> {
+ let s = s.into_c_string()?;
+ let mut out = 0;
+ crate::init();
+ unsafe {
+ try_call!(raw::git_config_parse_bool(&mut out, s));
+ }
+ Ok(out != 0)
+ }
+
+ /// Parse a string as an i32; handles suffixes like k, M, or G, and
+ /// multiplies by the appropriate power of 1024.
+ pub fn parse_i32<S: IntoCString>(s: S) -> Result<i32, Error> {
+ let s = s.into_c_string()?;
+ let mut out = 0;
+ crate::init();
+ unsafe {
+ try_call!(raw::git_config_parse_int32(&mut out, s));
+ }
+ Ok(out)
+ }
+
+ /// Parse a string as an i64; handles suffixes like k, M, or G, and
+ /// multiplies by the appropriate power of 1024.
+ pub fn parse_i64<S: IntoCString>(s: S) -> Result<i64, Error> {
+ let s = s.into_c_string()?;
+ let mut out = 0;
+ crate::init();
+ unsafe {
+ try_call!(raw::git_config_parse_int64(&mut out, s));
+ }
+ Ok(out)
+ }
+}
+
+impl Binding for Config {
+ type Raw = *mut raw::git_config;
+ unsafe fn from_raw(raw: *mut raw::git_config) -> Config {
+ Config { raw }
+ }
+ fn raw(&self) -> *mut raw::git_config {
+ self.raw
+ }
+}
+
+impl Drop for Config {
+ fn drop(&mut self) {
+ unsafe { raw::git_config_free(self.raw) }
+ }
+}
+
+impl<'cfg> ConfigEntry<'cfg> {
+ /// Gets the name of this entry.
+ ///
+ /// May return `None` if the name is not valid utf-8
+ pub fn name(&self) -> Option<&str> {
+ str::from_utf8(self.name_bytes()).ok()
+ }
+
+ /// Gets the name of this entry as a byte slice.
+ pub fn name_bytes(&self) -> &[u8] {
+ unsafe { crate::opt_bytes(self, (*self.raw).name).unwrap() }
+ }
+
+ /// Gets the value of this entry.
+ ///
+ /// May return `None` if the value is not valid utf-8
+ ///
+ /// # Panics
+ ///
+ /// Panics when no value is defined.
+ pub fn value(&self) -> Option<&str> {
+ str::from_utf8(self.value_bytes()).ok()
+ }
+
+ /// Gets the value of this entry as a byte slice.
+ ///
+ /// # Panics
+ ///
+ /// Panics when no value is defined.
+ pub fn value_bytes(&self) -> &[u8] {
+ unsafe { crate::opt_bytes(self, (*self.raw).value).unwrap() }
+ }
+
+ /// Returns `true` when a value is defined otherwise `false`.
+ ///
+ /// No value defined is a short-hand to represent a Boolean `true`.
+ pub fn has_value(&self) -> bool {
+ unsafe { !(*self.raw).value.is_null() }
+ }
+
+ /// Gets the configuration level of this entry.
+ pub fn level(&self) -> ConfigLevel {
+ unsafe { ConfigLevel::from_raw((*self.raw).level) }
+ }
+
+ /// Depth of includes where this variable was found
+ pub fn include_depth(&self) -> u32 {
+ unsafe { (*self.raw).include_depth as u32 }
+ }
+}
+
+impl<'cfg> Binding for ConfigEntry<'cfg> {
+ type Raw = *mut raw::git_config_entry;
+
+ unsafe fn from_raw(raw: *mut raw::git_config_entry) -> ConfigEntry<'cfg> {
+ ConfigEntry {
+ raw,
+ _marker: marker::PhantomData,
+ owned: true,
+ }
+ }
+ fn raw(&self) -> *mut raw::git_config_entry {
+ self.raw
+ }
+}
+
+impl<'cfg> Binding for ConfigEntries<'cfg> {
+ type Raw = *mut raw::git_config_iterator;
+
+ unsafe fn from_raw(raw: *mut raw::git_config_iterator) -> ConfigEntries<'cfg> {
+ ConfigEntries {
+ raw,
+ current: None,
+ _marker: marker::PhantomData,
+ }
+ }
+ fn raw(&self) -> *mut raw::git_config_iterator {
+ self.raw
+ }
+}
+
+impl<'cfg> ConfigEntries<'cfg> {
+ /// Advances the iterator and returns the next value.
+ ///
+ /// Returns `None` when iteration is finished.
+ pub fn next(&mut self) -> Option<Result<&ConfigEntry<'cfg>, Error>> {
+ let mut raw = ptr::null_mut();
+ drop(self.current.take());
+ unsafe {
+ try_call_iter!(raw::git_config_next(&mut raw, self.raw));
+ let entry = ConfigEntry {
+ owned: false,
+ raw,
+ _marker: marker::PhantomData,
+ };
+ self.current = Some(entry);
+ Some(Ok(self.current.as_ref().unwrap()))
+ }
+ }
+
+ /// Calls the given closure for each remaining entry in the iterator.
+ pub fn for_each<F: FnMut(&ConfigEntry<'cfg>)>(mut self, mut f: F) -> Result<(), Error> {
+ while let Some(entry) = self.next() {
+ let entry = entry?;
+ f(entry);
+ }
+ Ok(())
+ }
+}
+
+impl<'cfg> Drop for ConfigEntries<'cfg> {
+ fn drop(&mut self) {
+ unsafe { raw::git_config_iterator_free(self.raw) }
+ }
+}
+
+impl<'cfg> Drop for ConfigEntry<'cfg> {
+ fn drop(&mut self) {
+ if self.owned {
+ unsafe { raw::git_config_entry_free(self.raw) }
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use std::fs::File;
+ use tempfile::TempDir;
+
+ use crate::Config;
+
+ #[test]
+ fn smoke() {
+ let _cfg = Config::new().unwrap();
+ let _ = Config::find_global();
+ let _ = Config::find_system();
+ let _ = Config::find_xdg();
+ }
+
+ #[test]
+ fn persisted() {
+ let td = TempDir::new().unwrap();
+ let path = td.path().join("foo");
+ File::create(&path).unwrap();
+
+ let mut cfg = Config::open(&path).unwrap();
+ assert!(cfg.get_bool("foo.bar").is_err());
+ cfg.set_bool("foo.k1", true).unwrap();
+ cfg.set_i32("foo.k2", 1).unwrap();
+ cfg.set_i64("foo.k3", 2).unwrap();
+ cfg.set_str("foo.k4", "bar").unwrap();
+ cfg.snapshot().unwrap();
+ drop(cfg);
+
+ let cfg = Config::open(&path).unwrap().snapshot().unwrap();
+ assert_eq!(cfg.get_bool("foo.k1").unwrap(), true);
+ assert_eq!(cfg.get_i32("foo.k2").unwrap(), 1);
+ assert_eq!(cfg.get_i64("foo.k3").unwrap(), 2);
+ assert_eq!(cfg.get_str("foo.k4").unwrap(), "bar");
+
+ let mut entries = cfg.entries(None).unwrap();
+ while let Some(entry) = entries.next() {
+ let entry = entry.unwrap();
+ entry.name();
+ entry.value();
+ entry.level();
+ }
+ }
+
+ #[test]
+ fn multivar() {
+ let td = TempDir::new().unwrap();
+ let path = td.path().join("foo");
+ File::create(&path).unwrap();
+
+ let mut cfg = Config::open(&path).unwrap();
+ cfg.set_multivar("foo.bar", "^$", "baz").unwrap();
+ cfg.set_multivar("foo.bar", "^$", "qux").unwrap();
+ cfg.set_multivar("foo.bar", "^$", "quux").unwrap();
+ cfg.set_multivar("foo.baz", "^$", "oki").unwrap();
+
+ // `entries` filters by name
+ let mut entries: Vec<String> = Vec::new();
+ cfg.entries(Some("foo.bar"))
+ .unwrap()
+ .for_each(|entry| entries.push(entry.value().unwrap().to_string()))
+ .unwrap();
+ entries.sort();
+ assert_eq!(entries, ["baz", "quux", "qux"]);
+
+ // which is the same as `multivar` without a regex
+ let mut multivals = Vec::new();
+ cfg.multivar("foo.bar", None)
+ .unwrap()
+ .for_each(|entry| multivals.push(entry.value().unwrap().to_string()))
+ .unwrap();
+ multivals.sort();
+ assert_eq!(multivals, entries);
+
+ // yet _with_ a regex, `multivar` filters by value
+ let mut quxish = Vec::new();
+ cfg.multivar("foo.bar", Some("qu.*x"))
+ .unwrap()
+ .for_each(|entry| quxish.push(entry.value().unwrap().to_string()))
+ .unwrap();
+ quxish.sort();
+ assert_eq!(quxish, ["quux", "qux"]);
+
+ cfg.remove_multivar("foo.bar", ".*").unwrap();
+
+ let count = |entries: super::ConfigEntries<'_>| -> usize {
+ let mut c = 0;
+ entries.for_each(|_| c += 1).unwrap();
+ c
+ };
+
+ assert_eq!(count(cfg.entries(Some("foo.bar")).unwrap()), 0);
+ assert_eq!(count(cfg.multivar("foo.bar", None).unwrap()), 0);
+ }
+
+ #[test]
+ fn parse() {
+ assert_eq!(Config::parse_bool("").unwrap(), false);
+ assert_eq!(Config::parse_bool("false").unwrap(), false);
+ assert_eq!(Config::parse_bool("no").unwrap(), false);
+ assert_eq!(Config::parse_bool("off").unwrap(), false);
+ assert_eq!(Config::parse_bool("0").unwrap(), false);
+
+ assert_eq!(Config::parse_bool("true").unwrap(), true);
+ assert_eq!(Config::parse_bool("yes").unwrap(), true);
+ assert_eq!(Config::parse_bool("on").unwrap(), true);
+ assert_eq!(Config::parse_bool("1").unwrap(), true);
+ assert_eq!(Config::parse_bool("42").unwrap(), true);
+
+ assert!(Config::parse_bool(" ").is_err());
+ assert!(Config::parse_bool("some-string").is_err());
+ assert!(Config::parse_bool("-").is_err());
+
+ assert_eq!(Config::parse_i32("0").unwrap(), 0);
+ assert_eq!(Config::parse_i32("1").unwrap(), 1);
+ assert_eq!(Config::parse_i32("100").unwrap(), 100);
+ assert_eq!(Config::parse_i32("-1").unwrap(), -1);
+ assert_eq!(Config::parse_i32("-100").unwrap(), -100);
+ assert_eq!(Config::parse_i32("1k").unwrap(), 1024);
+ assert_eq!(Config::parse_i32("4k").unwrap(), 4096);
+ assert_eq!(Config::parse_i32("1M").unwrap(), 1048576);
+ assert_eq!(Config::parse_i32("1G").unwrap(), 1024 * 1024 * 1024);
+
+ assert_eq!(Config::parse_i64("0").unwrap(), 0);
+ assert_eq!(Config::parse_i64("1").unwrap(), 1);
+ assert_eq!(Config::parse_i64("100").unwrap(), 100);
+ assert_eq!(Config::parse_i64("-1").unwrap(), -1);
+ assert_eq!(Config::parse_i64("-100").unwrap(), -100);
+ assert_eq!(Config::parse_i64("1k").unwrap(), 1024);
+ assert_eq!(Config::parse_i64("4k").unwrap(), 4096);
+ assert_eq!(Config::parse_i64("1M").unwrap(), 1048576);
+ assert_eq!(Config::parse_i64("1G").unwrap(), 1024 * 1024 * 1024);
+ assert_eq!(Config::parse_i64("100G").unwrap(), 100 * 1024 * 1024 * 1024);
+ }
+}