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};
pub struct Config {
raw: *mut raw::git_config,
}
pub struct ConfigEntry<'cfg> {
raw: *mut raw::git_config_entry,
_marker: marker::PhantomData<&'cfg Config>,
owned: bool,
}
pub struct ConfigEntries<'cfg> {
raw: *mut raw::git_config_iterator,
_marker: marker::PhantomData<&'cfg Config>,
}
impl Config {
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))
}
}
pub fn open(path: &Path) -> Result<Config, Error> {
crate::init();
let mut raw = ptr::null_mut();
let path = path.into_c_string()?;
unsafe {
try_call!(raw::git_config_open_ondisk(&mut raw, path));
Ok(Binding::from_raw(raw))
}
}
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))
}
}
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())
}
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())
}
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())
}
pub fn add_file(&mut self, path: &Path, level: ConfigLevel, force: bool) -> Result<(), Error> {
let path = path.into_c_string()?;
unsafe {
try_call!(raw::git_config_add_file_ondisk(
self.raw,
path,
level,
ptr::null(),
force
));
Ok(())
}
}
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(())
}
}
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(())
}
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)
}
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)
}
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)
}
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"))
}
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())
}
}
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"))
}
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())
}
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))
}
}
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))
}
}
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))
}
}
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))
}
}
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))
}
}
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(())
}
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(())
}
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(())
}
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(())
}
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(())
}
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))
}
}
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)
}
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)
}
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: 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> {
pub fn name(&self) -> Option<&str> {
str::from_utf8(self.name_bytes()).ok()
}
pub fn name_bytes(&self) -> &[u8] {
unsafe { crate::opt_bytes(self, (*self.raw).name).unwrap() }
}
pub fn value(&self) -> Option<&str> {
str::from_utf8(self.value_bytes()).ok()
}
pub fn value_bytes(&self) -> &[u8] {
unsafe { crate::opt_bytes(self, (*self.raw).value).unwrap() }
}
pub fn has_value(&self) -> bool {
unsafe { !(*self.raw).value.is_null() }
}
pub fn level(&self) -> ConfigLevel {
unsafe { ConfigLevel::from_raw((*self.raw).level) }
}
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: 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: raw,
_marker: marker::PhantomData,
}
}
fn raw(&self) -> *mut raw::git_config_iterator {
self.raw
}
}
impl<'cfg, 'b> Iterator for &'b ConfigEntries<'cfg> {
type Item = Result<ConfigEntry<'b>, Error>;
fn next(&mut self) -> Option<Result<ConfigEntry<'b>, Error>> {
let mut raw = ptr::null_mut();
unsafe {
try_call_iter!(raw::git_config_next(&mut raw, self.raw));
Some(Ok(ConfigEntry {
owned: false,
raw: raw,
_marker: marker::PhantomData,
}))
}
}
}
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");
for entry in &cfg.entries(None).unwrap() {
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();
let mut entries: Vec<String> = cfg
.entries(Some("foo.bar"))
.unwrap()
.into_iter()
.map(|entry| entry.unwrap().value().unwrap().into())
.collect();
entries.sort();
assert_eq!(entries, ["baz", "quux", "qux"]);
let mut multivals: Vec<String> = cfg
.multivar("foo.bar", None)
.unwrap()
.into_iter()
.map(|entry| entry.unwrap().value().unwrap().into())
.collect();
multivals.sort();
assert_eq!(multivals, entries);
let mut quxish: Vec<String> = cfg
.multivar("foo.bar", Some("qu.*x"))
.unwrap()
.into_iter()
.map(|entry| entry.unwrap().value().unwrap().into())
.collect();
quxish.sort();
assert_eq!(quxish, ["quux", "qux"]);
cfg.remove_multivar("foo.bar", ".*").unwrap();
assert_eq!(cfg.entries(Some("foo.bar")).unwrap().count(), 0);
assert_eq!(cfg.multivar("foo.bar", None).unwrap().count(), 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);
}
}