use std::ffi::CString;
use std::fmt::{Debug, Formatter};
use std::ptr;
use gdal_sys::{CSLCount, CSLDestroy, CSLFetchNameValue, CSLSetNameValue};
use libc::c_char;
use crate::errors::{GdalError, Result};
use crate::utils::{_string, _string_tuple};
pub struct CslStringList {
list_ptr: *mut *mut c_char,
}
impl CslStringList {
pub fn new() -> Self {
Self {
list_ptr: ptr::null_mut(),
}
}
pub fn set_name_value(&mut self, name: &str, value: &str) -> Result<()> {
if !name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') {
return Err(GdalError::BadArgument(format!(
"Invalid characters in name: '{}'",
name
)));
}
if value.contains(|c| c == '\n' || c == '\r') {
return Err(GdalError::BadArgument(format!(
"Invalid characters in value: '{}'",
value
)));
}
let psz_name = CString::new(name)?;
let psz_value = CString::new(value)?;
unsafe {
self.list_ptr = CSLSetNameValue(self.list_ptr, psz_name.as_ptr(), psz_value.as_ptr());
}
Ok(())
}
pub fn fetch_name_value(&self, key: &str) -> Result<Option<String>> {
let key = CString::new(key)?;
let c_value = unsafe { CSLFetchNameValue(self.as_ptr(), key.as_ptr()) };
let value = if c_value.is_null() {
None
} else {
Some(_string(c_value))
};
Ok(value)
}
pub fn len(&self) -> usize {
(unsafe { CSLCount(self.as_ptr()) }) as usize
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn iter(&self) -> CslStringListIterator {
CslStringListIterator::new(self)
}
pub fn as_ptr(&self) -> gdal_sys::CSLConstList {
self.list_ptr
}
}
impl Drop for CslStringList {
fn drop(&mut self) {
unsafe { CSLDestroy(self.list_ptr) }
}
}
impl Default for CslStringList {
fn default() -> Self {
Self::new()
}
}
pub struct CslStringListIterator<'a> {
list: &'a CslStringList,
idx: usize,
count: usize,
}
impl<'a> CslStringListIterator<'a> {
fn new(list: &'a CslStringList) -> Self {
Self {
list,
idx: 0,
count: list.len(),
}
}
fn is_done(&self) -> bool {
self.idx >= self.count
}
}
impl<'a> Iterator for CslStringListIterator<'a> {
type Item = (String, String);
fn next(&mut self) -> Option<Self::Item> {
if self.is_done() {
return None;
}
let field = unsafe {
let slice = std::slice::from_raw_parts(self.list.list_ptr, self.count);
slice[self.idx]
};
if field.is_null() {
None
} else {
self.idx += 1;
_string_tuple(field, '=')
}
}
}
impl Debug for CslStringList {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
for (k, v) in self.iter() {
f.write_fmt(format_args!("{k}={v}\n"))?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::cpl::CslStringList;
use crate::errors::Result;
fn fixture() -> Result<CslStringList> {
let mut l = CslStringList::new();
l.set_name_value("ONE", "1")?;
l.set_name_value("TWO", "2")?;
l.set_name_value("THREE", "3")?;
Ok(l)
}
#[test]
fn basic_list() -> Result<()> {
let l = fixture()?;
assert!(matches!(l.fetch_name_value("ONE"), Ok(Some(s)) if s == *"1"));
assert!(matches!(l.fetch_name_value("THREE"), Ok(Some(s)) if s == *"3"));
assert!(matches!(l.fetch_name_value("FOO"), Ok(None)));
Ok(())
}
#[test]
fn has_length() -> Result<()> {
let l = fixture()?;
assert_eq!(l.len(), 3);
Ok(())
}
#[test]
fn can_be_empty() -> Result<()> {
let l = CslStringList::new();
assert!(l.is_empty());
let l = fixture()?;
assert!(!l.is_empty());
Ok(())
}
#[test]
fn has_iterator() -> Result<()> {
let f = fixture()?;
let mut it = f.iter();
assert_eq!(it.next(), Some(("ONE".to_string(), "1".to_string())));
assert_eq!(it.next(), Some(("TWO".to_string(), "2".to_string())));
assert_eq!(it.next(), Some(("THREE".to_string(), "3".to_string())));
assert_eq!(it.next(), None);
assert_eq!(it.next(), None);
Ok(())
}
#[test]
fn invalid_keys() -> Result<()> {
let mut l = fixture()?;
assert!(l.set_name_value("l==t", "2").is_err());
assert!(l.set_name_value("foo", "2\n4\r5").is_err());
Ok(())
}
#[test]
fn debug_fmt() -> Result<()> {
let l = fixture()?;
let s = format!("{l:?}");
assert!(s.contains("ONE=1"));
assert!(s.contains("TWO=2"));
assert!(s.contains("THREE=3"));
Ok(())
}
}