Kontinuation commented on code in PR #696:
URL: https://github.com/apache/sedona-db/pull/696#discussion_r2907215904


##########
c/sedona-gdal/src/cpl.rs:
##########
@@ -0,0 +1,661 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+//! Ported (and contains copied code) from georust/gdal:
+//! <https://github.com/georust/gdal/blob/v0.19.0/src/cpl.rs>.
+//! Original code is licensed under MIT.
+//!
+//! GDAL Common Portability Library Functions.
+//!
+//! Provides [`CslStringList`], a pure-Rust implementation of GDAL's 
null-terminated
+//! string list (`char **papszStrList`), compatible with the georust/gdal API 
surface.
+
+use std::ffi::{c_char, CString};
+use std::fmt::{Debug, Display, Formatter};
+use std::ptr;
+
+use crate::errors::{GdalError, Result};
+
+/// A null-terminated array of null-terminated C strings (`char 
**papszStrList`).
+///
+/// This data structure is used throughout GDAL to pass `KEY=VALUE`-formatted 
options
+/// to various functions.
+///
+/// This is a pure Rust implementation that mirrors the API of georust/gdal's
+/// `CslStringList`. Memory is managed entirely in Rust — no GDAL `CSL*` 
functions
+/// are called for list management. This should be fine as long as GDAL does 
not
+/// take ownership of the string lists and free them using `CSLDestroy`.
+///
+/// # Example
+///
+/// There are a number of ways to populate a [`CslStringList`]:
+///
+/// ```rust,ignore
+/// use sedona_gdal::cpl::{CslStringList, CslStringListEntry};
+///
+/// let mut sl1 = CslStringList::new();
+/// sl1.set_name_value("NUM_THREADS", "ALL_CPUS").unwrap();
+/// sl1.set_name_value("COMPRESS", "LZW").unwrap();
+/// sl1.add_string("MAGIC_FLAG").unwrap();
+///
+/// let sl2: CslStringList = "NUM_THREADS=ALL_CPUS COMPRESS=LZW 
MAGIC_FLAG".parse().unwrap();
+/// let sl3 = CslStringList::from_iter(["NUM_THREADS=ALL_CPUS", 
"COMPRESS=LZW", "MAGIC_FLAG"]);
+///
+/// assert_eq!(sl1.to_string(), sl2.to_string());
+/// assert_eq!(sl2.to_string(), sl3.to_string());
+/// ```
+pub struct CslStringList {
+    /// Owned strings.
+    strings: Vec<CString>,
+    /// Null-terminated pointer array into `strings`, rebuilt on every 
mutation.
+    /// Invariant: `ptrs.len() == strings.len() + 1` and `ptrs.last() == 
Some(&null_mut())`.
+    ptrs: Vec<*mut c_char>,
+}
+
+// Safety: CslStringList is Send + Sync because:
+// - `strings` (Vec<CString>) is Send + Sync.
+// - `ptrs` contains pointers derived from `strings` (stable heap-allocated 
CString data).
+//   They are only used for read-only FFI calls.
+unsafe impl Send for CslStringList {}
+unsafe impl Sync for CslStringList {}
+
+impl CslStringList {
+    /// Creates an empty GDAL string list.
+    pub fn new() -> Self {
+        Self::with_capacity(0)
+    }
+
+    /// Create an empty GDAL string list with given capacity.
+    pub fn with_capacity(capacity: usize) -> Self {
+        Self {
+            strings: Vec::with_capacity(capacity),
+            ptrs: vec![ptr::null_mut(); capacity + 1],
+        }
+    }
+
+    /// Rebuilds the null-terminated pointer array from `self.strings`.
+    ///
+    /// Must be called after every mutation to `self.strings`.
+    /// This is O(n) but n is always small (option lists are typically < 20 
entries).
+    ///
+    /// Safety argument: `CString` stores its data on the heap. Moving a 
`CString`
+    /// (as happens during `Vec` reallocation) does not invalidate the heap 
pointer
+    /// returned by `CString::as_ptr()`. Therefore pointers stored in 
`self.ptrs`
+    /// remain valid as long as the corresponding `CString` in `self.strings` 
is alive.
+    fn rebuild_ptrs(&mut self) {
+        self.ptrs.clear();
+        for s in &self.strings {
+            self.ptrs.push(s.as_ptr() as *mut c_char);
+        }
+        self.ptrs.push(ptr::null_mut());
+    }
+
+    /// Check that the given `name` is a valid [`CslStringList`] key.
+    ///
+    /// Per [GDAL 
documentation](https://gdal.org/api/cpl.html#_CPPv415CSLSetNameValuePPcPKcPKc),
+    /// a key cannot have non-alphanumeric characters in it (underscores are 
allowed).
+    ///
+    /// Returns `Err(GdalError::BadArgument)` on invalid name, `Ok(())` 
otherwise.
+    fn check_valid_name(name: &str) -> Result<()> {
+        if !name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') {
+            Err(GdalError::BadArgument(format!(
+                "Invalid characters in name: '{name}'"
+            )))
+        } else {
+            Ok(())
+        }
+    }
+
+    /// Check that the given `value` is a valid [`CslStringList`] value.
+    ///
+    /// Per [GDAL 
documentation](https://gdal.org/api/cpl.html#_CPPv415CSLSetNameValuePPcPKcPKc),
+    /// a value cannot have newline characters in it.
+    ///
+    /// Returns `Err(GdalError::BadArgument)` on invalid value, `Ok(())` 
otherwise.
+    fn check_valid_value(value: &str) -> Result<()> {
+        if value.contains(['\n', '\r']) {
+            Err(GdalError::BadArgument(format!(
+                "Invalid characters in value: '{value}'"
+            )))
+        } else {
+            Ok(())
+        }
+    }
+
+    /// Assigns `value` to the key `name` without checking for pre-existing 
assignments.
+    ///
+    /// Returns `Ok(())` on success, or `Err(GdalError::BadArgument)`
+    /// if `name` has non-alphanumeric characters or `value` has newline 
characters.
+    ///
+    /// See: 
[`CSLAddNameValue`](https://gdal.org/api/cpl.html#_CPPv415CSLAddNameValuePPcPKcPKc)
+    /// for details.
+    pub fn add_name_value(&mut self, name: &str, value: &str) -> Result<()> {
+        Self::check_valid_name(name)?;
+        Self::check_valid_value(value)?;
+        let entry = CString::new(format!("{name}={value}"))?;
+        self.strings.push(entry);
+        self.rebuild_ptrs();
+        Ok(())
+    }
+
+    /// Assigns `value` to the key `name`, overwriting any existing assignment 
to `name`.
+    ///
+    /// Name lookup is case-insensitive, matching GDAL's `CSLSetNameValue` 
behavior.
+    ///
+    /// Returns `Ok(())` on success, or `Err(GdalError::BadArgument)`
+    /// if `name` has non-alphanumeric characters or `value` has newline 
characters.
+    ///
+    /// See: 
[`CSLSetNameValue`](https://gdal.org/api/cpl.html#_CPPv415CSLSetNameValuePPcPKcPKc)
+    /// for details.
+    pub fn set_name_value(&mut self, name: &str, value: &str) -> Result<()> {
+        Self::check_valid_name(name)?;
+        Self::check_valid_value(value)?;
+        let existing = self.strings.iter().position(|s| {
+            s.to_str().is_ok_and(|v| {
+                v.split_once('=')
+                    .is_some_and(|(k, _)| k.eq_ignore_ascii_case(name))
+            })
+        });
+        let new_entry = CString::new(format!("{name}={value}"))?;
+        if let Some(idx) = existing {
+            self.strings[idx] = new_entry;
+        } else {
+            self.strings.push(new_entry);
+        }
+        self.rebuild_ptrs();
+        Ok(())
+    }
+
+    /// Adds a copy of the string slice `value` to the list.
+    ///
+    /// Returns `Ok(())` on success, `Err(GdalError::FfiNulError)` if `value` 
cannot be
+    /// converted to a C string (e.g. `value` contains a `0` byte).
+    ///
+    /// See: 
[`CSLAddString`](https://gdal.org/api/cpl.html#_CPPv412CSLAddStringPPcPKc)
+    pub fn add_string(&mut self, value: &str) -> Result<()> {
+        let v = CString::new(value)?;
+        self.strings.push(v);
+        self.rebuild_ptrs();
+        Ok(())
+    }
+
+    /// Adds the contents of a [`CslStringListEntry`] to `self`.
+    ///
+    /// Returns `Err(GdalError::BadArgument)` if entry doesn't meet entry 
restrictions as
+    /// described by [`CslStringListEntry`].
+    pub fn add_entry(&mut self, entry: &CslStringListEntry) -> Result<()> {
+        match entry {
+            CslStringListEntry::Flag(f) => self.add_string(f),
+            CslStringListEntry::Pair { name, value } => 
self.add_name_value(name, value),
+        }
+    }
+
+    /// Looks up the value corresponding to `name` (case-insensitive).
+    ///
+    /// See 
[`CSLFetchNameValue`](https://gdal.org/doxygen/cpl__string_8h.html#a4f23675f8b6f015ed23d9928048361a1)
+    /// for details.
+    pub fn fetch_name_value(&self, name: &str) -> Option<String> {
+        for s in &self.strings {
+            if let Ok(v) = s.to_str() {
+                if let Some((k, val)) = v.split_once('=') {
+                    if k.eq_ignore_ascii_case(name) {
+                        return Some(val.to_string());
+                    }
+                }
+            }
+        }
+        None
+    }
+
+    /// Perform a case **insensitive** search for the given string.
+    ///
+    /// Returns `Some(usize)` of value index position, or `None` if not found.
+    ///
+    /// See: 
[`CSLFindString`](https://gdal.org/api/cpl.html#_CPPv413CSLFindString12CSLConstListPKc)
+    /// for details.
+    pub fn find_string(&self, value: &str) -> Option<usize> {
+        self.strings
+            .iter()
+            .position(|s| s.to_str().is_ok_and(|v| 
v.eq_ignore_ascii_case(value)))
+    }
+
+    /// Perform a case sensitive search for the given string.
+    ///
+    /// Returns `Some(usize)` of value index position, or `None` if not found.
+    pub fn find_string_case_sensitive(&self, value: &str) -> Option<usize> {
+        self.strings.iter().position(|s| s.to_str() == Ok(value))
+    }
+
+    /// Perform a case sensitive partial string search indicated by `fragment`.
+    ///
+    /// Returns `Some(usize)` of value index position, or `None` if not found.
+    ///
+    /// See: 
[`CSLPartialFindString`](https://gdal.org/api/cpl.html#_CPPv420CSLPartialFindString12CSLConstListPKc)
+    /// for details.
+    pub fn partial_find_string(&self, fragment: &str) -> Option<usize> {
+        self.strings
+            .iter()
+            .position(|s| s.to_str().is_ok_and(|v| v.contains(fragment)))
+    }
+
+    /// Fetch the [`CslStringListEntry`] for the entry at the given index.
+    ///
+    /// Returns `None` if index is out of bounds, `Some(entry)` otherwise.
+    pub fn get_field(&self, index: usize) -> Option<CslStringListEntry> {
+        self.strings
+            .get(index)
+            .and_then(|s| s.to_str().ok())
+            .map(CslStringListEntry::from)
+    }
+
+    /// Determine the number of entries in the list.
+    ///
+    /// See: 
[`CSLCount`](https://gdal.org/api/cpl.html#_CPPv48CSLCount12CSLConstList) for 
details.
+    pub fn len(&self) -> usize {
+        self.strings.len()
+    }
+
+    /// Determine if the list has any values.
+    pub fn is_empty(&self) -> bool {
+        self.strings.is_empty()
+    }
+
+    /// Get an iterator over the entries of the list.
+    pub fn iter(&self) -> CslStringListIterator<'_> {
+        CslStringListIterator { list: self, idx: 0 }
+    }
+
+    /// Get the raw null-terminated `char**` pointer for passing to GDAL 
functions.
+    ///
+    /// The returned pointer is valid as long as `self` is alive and not 
mutated.
+    /// An empty list returns a pointer to `[null]`, which is a valid empty 
CSL.
+    pub fn as_ptr(&self) -> *mut *mut c_char {
+        self.ptrs.as_ptr() as *mut *mut c_char
+    }
+
+    /// Construct a `CslStringList` from a fallible iterator of string slices.
+    ///
+    /// Unlike `FromIterator<&str>`, this returns `Err` if any string contains 
NUL bytes
+    /// instead of panicking.
+    pub fn try_from_iter<'a>(iter: impl IntoIterator<Item = &'a str>) -> 
Result<Self> {
+        let mut list = Self::new();
+        for s in iter {
+            list.add_string(s)?;
+        }
+        Ok(list)
+    }
+}
+
+impl Default for CslStringList {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl Clone for CslStringList {
+    fn clone(&self) -> Self {
+        let strings = self.strings.clone();
+        let mut result = Self {
+            strings,
+            ptrs: Vec::new(),
+        };
+        result.rebuild_ptrs();
+        result
+    }
+}
+
+impl Debug for CslStringList {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        let mut b = f.debug_tuple("CslStringList");
+        for e in self.iter() {
+            b.field(&e.to_string());
+        }
+        b.finish()
+    }
+}
+
+impl Display for CslStringList {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        for e in self.iter() {
+            f.write_fmt(format_args!("{e}\n"))?;
+        }
+        Ok(())
+    }
+}
+
+impl<'a> IntoIterator for &'a CslStringList {
+    type Item = CslStringListEntry;
+    type IntoIter = CslStringListIterator<'a>;
+
+    fn into_iter(self) -> Self::IntoIter {
+        self.iter()
+    }
+}
+
+impl FromIterator<CslStringListEntry> for CslStringList {
+    fn from_iter<T: IntoIterator<Item = CslStringListEntry>>(iter: T) -> Self {
+        let mut result = Self::default();
+        for e in iter {
+            result.add_entry(&e).unwrap_or_default();
+        }
+        result
+    }
+}

Review Comment:
   This follows the same design as georust/gdal, but it is probably not a good 
one. I'm considering changing this interface.



##########
c/sedona-gdal/src/cpl.rs:
##########
@@ -0,0 +1,661 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+//! Ported (and contains copied code) from georust/gdal:
+//! <https://github.com/georust/gdal/blob/v0.19.0/src/cpl.rs>.
+//! Original code is licensed under MIT.
+//!
+//! GDAL Common Portability Library Functions.
+//!
+//! Provides [`CslStringList`], a pure-Rust implementation of GDAL's 
null-terminated
+//! string list (`char **papszStrList`), compatible with the georust/gdal API 
surface.
+
+use std::ffi::{c_char, CString};
+use std::fmt::{Debug, Display, Formatter};
+use std::ptr;
+
+use crate::errors::{GdalError, Result};
+
+/// A null-terminated array of null-terminated C strings (`char 
**papszStrList`).
+///
+/// This data structure is used throughout GDAL to pass `KEY=VALUE`-formatted 
options
+/// to various functions.
+///
+/// This is a pure Rust implementation that mirrors the API of georust/gdal's
+/// `CslStringList`. Memory is managed entirely in Rust — no GDAL `CSL*` 
functions
+/// are called for list management. This should be fine as long as GDAL does 
not
+/// take ownership of the string lists and free them using `CSLDestroy`.
+///
+/// # Example
+///
+/// There are a number of ways to populate a [`CslStringList`]:
+///
+/// ```rust,ignore
+/// use sedona_gdal::cpl::{CslStringList, CslStringListEntry};
+///
+/// let mut sl1 = CslStringList::new();
+/// sl1.set_name_value("NUM_THREADS", "ALL_CPUS").unwrap();
+/// sl1.set_name_value("COMPRESS", "LZW").unwrap();
+/// sl1.add_string("MAGIC_FLAG").unwrap();
+///
+/// let sl2: CslStringList = "NUM_THREADS=ALL_CPUS COMPRESS=LZW 
MAGIC_FLAG".parse().unwrap();
+/// let sl3 = CslStringList::from_iter(["NUM_THREADS=ALL_CPUS", 
"COMPRESS=LZW", "MAGIC_FLAG"]);
+///
+/// assert_eq!(sl1.to_string(), sl2.to_string());
+/// assert_eq!(sl2.to_string(), sl3.to_string());
+/// ```
+pub struct CslStringList {
+    /// Owned strings.
+    strings: Vec<CString>,
+    /// Null-terminated pointer array into `strings`, rebuilt on every 
mutation.
+    /// Invariant: `ptrs.len() == strings.len() + 1` and `ptrs.last() == 
Some(&null_mut())`.
+    ptrs: Vec<*mut c_char>,
+}
+
+// Safety: CslStringList is Send + Sync because:
+// - `strings` (Vec<CString>) is Send + Sync.
+// - `ptrs` contains pointers derived from `strings` (stable heap-allocated 
CString data).
+//   They are only used for read-only FFI calls.
+unsafe impl Send for CslStringList {}
+unsafe impl Sync for CslStringList {}
+
+impl CslStringList {
+    /// Creates an empty GDAL string list.
+    pub fn new() -> Self {
+        Self::with_capacity(0)
+    }
+
+    /// Create an empty GDAL string list with given capacity.
+    pub fn with_capacity(capacity: usize) -> Self {
+        Self {
+            strings: Vec::with_capacity(capacity),
+            ptrs: vec![ptr::null_mut(); capacity + 1],
+        }
+    }
+
+    /// Rebuilds the null-terminated pointer array from `self.strings`.
+    ///
+    /// Must be called after every mutation to `self.strings`.
+    /// This is O(n) but n is always small (option lists are typically < 20 
entries).
+    ///
+    /// Safety argument: `CString` stores its data on the heap. Moving a 
`CString`
+    /// (as happens during `Vec` reallocation) does not invalidate the heap 
pointer
+    /// returned by `CString::as_ptr()`. Therefore pointers stored in 
`self.ptrs`
+    /// remain valid as long as the corresponding `CString` in `self.strings` 
is alive.
+    fn rebuild_ptrs(&mut self) {
+        self.ptrs.clear();
+        for s in &self.strings {
+            self.ptrs.push(s.as_ptr() as *mut c_char);
+        }
+        self.ptrs.push(ptr::null_mut());
+    }
+
+    /// Check that the given `name` is a valid [`CslStringList`] key.
+    ///
+    /// Per [GDAL 
documentation](https://gdal.org/api/cpl.html#_CPPv415CSLSetNameValuePPcPKcPKc),
+    /// a key cannot have non-alphanumeric characters in it (underscores are 
allowed).
+    ///
+    /// Returns `Err(GdalError::BadArgument)` on invalid name, `Ok(())` 
otherwise.
+    fn check_valid_name(name: &str) -> Result<()> {
+        if !name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') {
+            Err(GdalError::BadArgument(format!(
+                "Invalid characters in name: '{name}'"
+            )))
+        } else {
+            Ok(())
+        }
+    }
+
+    /// Check that the given `value` is a valid [`CslStringList`] value.
+    ///
+    /// Per [GDAL 
documentation](https://gdal.org/api/cpl.html#_CPPv415CSLSetNameValuePPcPKcPKc),
+    /// a value cannot have newline characters in it.
+    ///
+    /// Returns `Err(GdalError::BadArgument)` on invalid value, `Ok(())` 
otherwise.
+    fn check_valid_value(value: &str) -> Result<()> {
+        if value.contains(['\n', '\r']) {
+            Err(GdalError::BadArgument(format!(
+                "Invalid characters in value: '{value}'"
+            )))
+        } else {
+            Ok(())
+        }
+    }
+
+    /// Assigns `value` to the key `name` without checking for pre-existing 
assignments.
+    ///
+    /// Returns `Ok(())` on success, or `Err(GdalError::BadArgument)`
+    /// if `name` has non-alphanumeric characters or `value` has newline 
characters.
+    ///
+    /// See: 
[`CSLAddNameValue`](https://gdal.org/api/cpl.html#_CPPv415CSLAddNameValuePPcPKcPKc)
+    /// for details.
+    pub fn add_name_value(&mut self, name: &str, value: &str) -> Result<()> {
+        Self::check_valid_name(name)?;
+        Self::check_valid_value(value)?;
+        let entry = CString::new(format!("{name}={value}"))?;
+        self.strings.push(entry);
+        self.rebuild_ptrs();
+        Ok(())
+    }
+
+    /// Assigns `value` to the key `name`, overwriting any existing assignment 
to `name`.
+    ///
+    /// Name lookup is case-insensitive, matching GDAL's `CSLSetNameValue` 
behavior.
+    ///
+    /// Returns `Ok(())` on success, or `Err(GdalError::BadArgument)`
+    /// if `name` has non-alphanumeric characters or `value` has newline 
characters.
+    ///
+    /// See: 
[`CSLSetNameValue`](https://gdal.org/api/cpl.html#_CPPv415CSLSetNameValuePPcPKcPKc)
+    /// for details.
+    pub fn set_name_value(&mut self, name: &str, value: &str) -> Result<()> {
+        Self::check_valid_name(name)?;
+        Self::check_valid_value(value)?;
+        let existing = self.strings.iter().position(|s| {
+            s.to_str().is_ok_and(|v| {
+                v.split_once('=')
+                    .is_some_and(|(k, _)| k.eq_ignore_ascii_case(name))
+            })
+        });
+        let new_entry = CString::new(format!("{name}={value}"))?;
+        if let Some(idx) = existing {
+            self.strings[idx] = new_entry;
+        } else {
+            self.strings.push(new_entry);
+        }
+        self.rebuild_ptrs();
+        Ok(())
+    }
+
+    /// Adds a copy of the string slice `value` to the list.
+    ///
+    /// Returns `Ok(())` on success, `Err(GdalError::FfiNulError)` if `value` 
cannot be
+    /// converted to a C string (e.g. `value` contains a `0` byte).
+    ///
+    /// See: 
[`CSLAddString`](https://gdal.org/api/cpl.html#_CPPv412CSLAddStringPPcPKc)
+    pub fn add_string(&mut self, value: &str) -> Result<()> {
+        let v = CString::new(value)?;
+        self.strings.push(v);
+        self.rebuild_ptrs();
+        Ok(())
+    }
+
+    /// Adds the contents of a [`CslStringListEntry`] to `self`.
+    ///
+    /// Returns `Err(GdalError::BadArgument)` if entry doesn't meet entry 
restrictions as
+    /// described by [`CslStringListEntry`].
+    pub fn add_entry(&mut self, entry: &CslStringListEntry) -> Result<()> {
+        match entry {
+            CslStringListEntry::Flag(f) => self.add_string(f),
+            CslStringListEntry::Pair { name, value } => 
self.add_name_value(name, value),
+        }
+    }
+
+    /// Looks up the value corresponding to `name` (case-insensitive).
+    ///
+    /// See 
[`CSLFetchNameValue`](https://gdal.org/doxygen/cpl__string_8h.html#a4f23675f8b6f015ed23d9928048361a1)
+    /// for details.
+    pub fn fetch_name_value(&self, name: &str) -> Option<String> {
+        for s in &self.strings {
+            if let Ok(v) = s.to_str() {
+                if let Some((k, val)) = v.split_once('=') {
+                    if k.eq_ignore_ascii_case(name) {
+                        return Some(val.to_string());
+                    }
+                }
+            }
+        }
+        None
+    }
+
+    /// Perform a case **insensitive** search for the given string.
+    ///
+    /// Returns `Some(usize)` of value index position, or `None` if not found.
+    ///
+    /// See: 
[`CSLFindString`](https://gdal.org/api/cpl.html#_CPPv413CSLFindString12CSLConstListPKc)
+    /// for details.
+    pub fn find_string(&self, value: &str) -> Option<usize> {
+        self.strings
+            .iter()
+            .position(|s| s.to_str().is_ok_and(|v| 
v.eq_ignore_ascii_case(value)))
+    }
+
+    /// Perform a case sensitive search for the given string.
+    ///
+    /// Returns `Some(usize)` of value index position, or `None` if not found.
+    pub fn find_string_case_sensitive(&self, value: &str) -> Option<usize> {
+        self.strings.iter().position(|s| s.to_str() == Ok(value))
+    }
+
+    /// Perform a case sensitive partial string search indicated by `fragment`.
+    ///
+    /// Returns `Some(usize)` of value index position, or `None` if not found.
+    ///
+    /// See: 
[`CSLPartialFindString`](https://gdal.org/api/cpl.html#_CPPv420CSLPartialFindString12CSLConstListPKc)
+    /// for details.
+    pub fn partial_find_string(&self, fragment: &str) -> Option<usize> {
+        self.strings
+            .iter()
+            .position(|s| s.to_str().is_ok_and(|v| v.contains(fragment)))
+    }
+
+    /// Fetch the [`CslStringListEntry`] for the entry at the given index.
+    ///
+    /// Returns `None` if index is out of bounds, `Some(entry)` otherwise.
+    pub fn get_field(&self, index: usize) -> Option<CslStringListEntry> {
+        self.strings
+            .get(index)
+            .and_then(|s| s.to_str().ok())
+            .map(CslStringListEntry::from)
+    }
+
+    /// Determine the number of entries in the list.
+    ///
+    /// See: 
[`CSLCount`](https://gdal.org/api/cpl.html#_CPPv48CSLCount12CSLConstList) for 
details.
+    pub fn len(&self) -> usize {
+        self.strings.len()
+    }
+
+    /// Determine if the list has any values.
+    pub fn is_empty(&self) -> bool {
+        self.strings.is_empty()
+    }
+
+    /// Get an iterator over the entries of the list.
+    pub fn iter(&self) -> CslStringListIterator<'_> {
+        CslStringListIterator { list: self, idx: 0 }
+    }
+
+    /// Get the raw null-terminated `char**` pointer for passing to GDAL 
functions.
+    ///
+    /// The returned pointer is valid as long as `self` is alive and not 
mutated.
+    /// An empty list returns a pointer to `[null]`, which is a valid empty 
CSL.
+    pub fn as_ptr(&self) -> *mut *mut c_char {
+        self.ptrs.as_ptr() as *mut *mut c_char
+    }
+
+    /// Construct a `CslStringList` from a fallible iterator of string slices.
+    ///
+    /// Unlike `FromIterator<&str>`, this returns `Err` if any string contains 
NUL bytes
+    /// instead of panicking.
+    pub fn try_from_iter<'a>(iter: impl IntoIterator<Item = &'a str>) -> 
Result<Self> {
+        let mut list = Self::new();
+        for s in iter {
+            list.add_string(s)?;
+        }
+        Ok(list)
+    }
+}
+
+impl Default for CslStringList {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl Clone for CslStringList {
+    fn clone(&self) -> Self {
+        let strings = self.strings.clone();
+        let mut result = Self {
+            strings,
+            ptrs: Vec::new(),
+        };
+        result.rebuild_ptrs();
+        result
+    }
+}
+
+impl Debug for CslStringList {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        let mut b = f.debug_tuple("CslStringList");
+        for e in self.iter() {
+            b.field(&e.to_string());
+        }
+        b.finish()
+    }
+}
+
+impl Display for CslStringList {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        for e in self.iter() {
+            f.write_fmt(format_args!("{e}\n"))?;
+        }
+        Ok(())
+    }
+}
+
+impl<'a> IntoIterator for &'a CslStringList {
+    type Item = CslStringListEntry;
+    type IntoIter = CslStringListIterator<'a>;
+
+    fn into_iter(self) -> Self::IntoIter {
+        self.iter()
+    }
+}
+
+impl FromIterator<CslStringListEntry> for CslStringList {
+    fn from_iter<T: IntoIterator<Item = CslStringListEntry>>(iter: T) -> Self {
+        let mut result = Self::default();
+        for e in iter {
+            result.add_entry(&e).unwrap_or_default();
+        }
+        result
+    }
+}
+
+impl<'a> FromIterator<&'a str> for CslStringList {
+    fn from_iter<T: IntoIterator<Item = &'a str>>(iter: T) -> Self {
+        iter.into_iter()
+            .map(Into::<CslStringListEntry>::into)
+            .collect()
+    }
+}
+
+impl FromIterator<String> for CslStringList {
+    fn from_iter<T: IntoIterator<Item = String>>(iter: T) -> Self {
+        iter.into_iter()
+            .map(Into::<CslStringListEntry>::into)
+            .collect()
+    }
+}
+
+impl Extend<CslStringListEntry> for CslStringList {
+    fn extend<T: IntoIterator<Item = CslStringListEntry>>(&mut self, iter: T) {
+        for e in iter {
+            self.add_entry(&e).unwrap_or_default();
+        }
+    }
+}

Review Comment:
   This follows the same design as georust/gdal, but it is probably not a good 
one. I'm considering changing this interface.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to