Skip to main content

apple_cf/cf/
collections.rs

1//! Core Foundation collection wrappers.
2//!
3#![allow(clippy::missing_panics_doc)]
4
5//! ```rust
6//! use apple_cf::cf::{CFArray, CFAttributedString, CFBag, CFDictionary, CFString, CFTree};
7//!
8//! let first = CFString::new("first");
9//! let second = CFString::new("second");
10//! let array = CFArray::from_values(&[&first, &second]);
11//! assert_eq!(array.len(), 2);
12//!
13//! let dict = CFDictionary::from_pairs(&[(&first, &second)]);
14//! assert!(dict.contains_key(&first));
15//!
16//! let bag = CFBag::from_values(&[&first, &first, &second]);
17//! assert_eq!(bag.count_of_value(&first), 2);
18//!
19//! let attributed = CFAttributedString::new(&first);
20//! assert_eq!(attributed.string().to_string(), "first");
21//!
22//! let root = CFTree::new(Some(&first));
23//! let child = CFTree::new(Some(&second));
24//! root.append_child(&child);
25//! assert_eq!(root.child_count(), 1);
26//! ```
27
28use super::base::{impl_cf_type_wrapper, AsCFType, CFType, SwiftObject};
29use super::CFString;
30use crate::ffi;
31use std::fmt;
32
33impl_cf_type_wrapper!(CFArray, cf_array_get_type_id);
34impl_cf_type_wrapper!(CFDictionary, cf_dictionary_get_type_id);
35pub type CFDict = CFDictionary;
36impl_cf_type_wrapper!(CFBag, cf_bag_get_type_id);
37impl_cf_type_wrapper!(CFAttributedString, cf_attributed_string_get_type_id);
38
39impl CFArray {
40    /// Create an array from borrowed Core Foundation values.
41    #[must_use]
42    pub fn from_values(values: &[&dyn AsCFType]) -> Self {
43        let raw_values: Vec<*mut std::ffi::c_void> =
44            values.iter().map(|value| value.as_ptr()).collect();
45        let ptr = unsafe { ffi::cf_array_create(raw_values.as_ptr(), raw_values.len()) };
46        Self::from_raw(ptr).expect("CFArrayCreate returned NULL")
47    }
48
49    /// Number of elements in the array.
50    #[must_use]
51    pub fn len(&self) -> usize {
52        unsafe { ffi::cf_array_get_count(self.as_ptr()) }
53    }
54
55    /// Whether the array is empty.
56    #[must_use]
57    pub fn is_empty(&self) -> bool {
58        self.len() == 0
59    }
60
61    /// Copy the value at `index`.
62    #[must_use]
63    pub fn get(&self, index: usize) -> Option<CFType> {
64        let ptr = unsafe { ffi::cf_array_get_value_at_index(self.as_ptr(), index) };
65        CFType::from_raw(ptr)
66    }
67
68    /// Copy all elements into a Rust vector.
69    #[must_use]
70    pub fn values(&self) -> Vec<CFType> {
71        (0..self.len())
72            .filter_map(|index| self.get(index))
73            .collect()
74    }
75}
76
77impl CFDictionary {
78    /// Create a dictionary from borrowed key/value pairs.
79    #[must_use]
80    pub fn from_pairs(pairs: &[(&dyn AsCFType, &dyn AsCFType)]) -> Self {
81        let keys: Vec<*mut std::ffi::c_void> = pairs.iter().map(|(key, _)| key.as_ptr()).collect();
82        let values: Vec<*mut std::ffi::c_void> =
83            pairs.iter().map(|(_, value)| value.as_ptr()).collect();
84        let ptr = unsafe { ffi::cf_dictionary_create(keys.as_ptr(), values.as_ptr(), pairs.len()) };
85        Self::from_raw(ptr).expect("CFDictionaryCreate returned NULL")
86    }
87
88    /// Number of key/value pairs.
89    #[must_use]
90    pub fn len(&self) -> usize {
91        unsafe { ffi::cf_dictionary_get_count(self.as_ptr()) }
92    }
93
94    /// Whether the dictionary is empty.
95    #[must_use]
96    pub fn is_empty(&self) -> bool {
97        self.len() == 0
98    }
99
100    /// Whether `key` exists in the dictionary.
101    #[must_use]
102    pub fn contains_key(&self, key: &dyn AsCFType) -> bool {
103        unsafe { ffi::cf_dictionary_contains_key(self.as_ptr(), key.as_ptr()) }
104    }
105
106    /// Copy the value associated with `key`.
107    #[must_use]
108    pub fn get(&self, key: &dyn AsCFType) -> Option<CFType> {
109        let ptr = unsafe { ffi::cf_dictionary_get_value(self.as_ptr(), key.as_ptr()) };
110        CFType::from_raw(ptr)
111    }
112
113    /// Copy all keys into a `CFArray`.
114    #[must_use]
115    pub fn keys(&self) -> CFArray {
116        let ptr = unsafe { ffi::cf_dictionary_copy_keys(self.as_ptr()) };
117        CFArray::from_raw(ptr).expect("CFDictionary keys array should be non-null")
118    }
119
120    /// Copy all values into a `CFArray`.
121    #[must_use]
122    pub fn values(&self) -> CFArray {
123        let ptr = unsafe { ffi::cf_dictionary_copy_values(self.as_ptr()) };
124        CFArray::from_raw(ptr).expect("CFDictionary values array should be non-null")
125    }
126}
127
128impl CFBag {
129    /// Create a bag from borrowed Core Foundation values.
130    #[must_use]
131    pub fn from_values(values: &[&dyn AsCFType]) -> Self {
132        let raw_values: Vec<*mut std::ffi::c_void> =
133            values.iter().map(|value| value.as_ptr()).collect();
134        let ptr = unsafe { ffi::cf_bag_create(raw_values.as_ptr(), raw_values.len()) };
135        Self::from_raw(ptr).expect("CFBagCreate returned NULL")
136    }
137
138    /// Number of values in the bag.
139    #[must_use]
140    pub fn len(&self) -> usize {
141        unsafe { ffi::cf_bag_get_count(self.as_ptr()) }
142    }
143
144    /// Whether the bag is empty.
145    #[must_use]
146    pub fn is_empty(&self) -> bool {
147        self.len() == 0
148    }
149
150    /// Whether the bag contains `candidate`.
151    #[must_use]
152    pub fn contains(&self, candidate: &dyn AsCFType) -> bool {
153        unsafe { ffi::cf_bag_contains_value(self.as_ptr(), candidate.as_ptr()) }
154    }
155
156    /// Number of times `candidate` appears in the bag.
157    #[must_use]
158    pub fn count_of_value(&self, candidate: &dyn AsCFType) -> usize {
159        unsafe { ffi::cf_bag_get_count_of_value(self.as_ptr(), candidate.as_ptr()) }
160    }
161}
162
163impl CFAttributedString {
164    /// Create an attributed string with no attributes.
165    #[must_use]
166    pub fn new(string: &CFString) -> Self {
167        let ptr = unsafe { ffi::cf_attributed_string_create(string.as_ptr()) };
168        Self::from_raw(ptr).expect("CFAttributedStringCreate returned NULL")
169    }
170
171    /// Underlying plain string.
172    #[must_use]
173    pub fn string(&self) -> CFString {
174        let ptr = unsafe { ffi::cf_attributed_string_get_string(self.as_ptr()) };
175        CFString::from_raw(ptr).expect("CFAttributedStringGetString returned NULL")
176    }
177
178    /// Character length.
179    #[must_use]
180    pub fn len(&self) -> usize {
181        unsafe { ffi::cf_attributed_string_get_length(self.as_ptr()) }
182    }
183
184    /// Whether the string is empty.
185    #[must_use]
186    pub fn is_empty(&self) -> bool {
187        self.len() == 0
188    }
189}
190
191/// Safe wrapper around a Swift-backed `CFTree` helper.
192#[derive(Clone, PartialEq, Eq, Hash)]
193pub struct CFTree(SwiftObject);
194
195impl CFTree {
196    /// Create a tree node with an optional Core Foundation payload.
197    #[must_use]
198    pub fn new(value: Option<&dyn AsCFType>) -> Self {
199        let ptr =
200            unsafe { ffi::cf_tree_create(value.map_or(std::ptr::null_mut(), AsCFType::as_ptr)) };
201        Self(SwiftObject::from_raw(ptr).expect("tree bridge returned NULL"))
202    }
203
204    #[must_use]
205    pub(crate) fn from_raw(ptr: *mut std::ffi::c_void) -> Option<Self> {
206        SwiftObject::from_raw(ptr).map(Self)
207    }
208
209    /// Borrow the raw tree handle.
210    #[must_use]
211    pub(crate) const fn as_ptr(&self) -> *mut std::ffi::c_void {
212        self.0.as_ptr()
213    }
214
215    /// Append `child` to this node.
216    pub fn append_child(&self, child: &Self) {
217        unsafe { ffi::cf_tree_append_child(self.as_ptr(), child.as_ptr()) };
218    }
219
220    /// Number of direct children.
221    #[must_use]
222    pub fn child_count(&self) -> usize {
223        unsafe { ffi::cf_tree_get_child_count(self.as_ptr()) }
224    }
225
226    /// Copy the child at `index`.
227    #[must_use]
228    pub fn child_at(&self, index: usize) -> Option<Self> {
229        let ptr = unsafe { ffi::cf_tree_get_child_at_index(self.as_ptr(), index) };
230        Self::from_raw(ptr)
231    }
232
233    /// Copy the payload value if present.
234    #[must_use]
235    pub fn value(&self) -> Option<CFType> {
236        let ptr = unsafe { ffi::cf_tree_copy_value(self.as_ptr()) };
237        CFType::from_raw(ptr)
238    }
239}
240
241impl fmt::Debug for CFTree {
242    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
243        f.debug_struct("CFTree")
244            .field("ptr", &self.as_ptr())
245            .field("child_count", &self.child_count())
246            .finish()
247    }
248}