neorg_dirman/
c_functions.rs1use crate::workspace::Workspace;
2use std::{
3 ffi::{c_char, CString},
4 mem::ManuallyDrop,
5};
6
7#[repr(C)]
8pub struct FileList {
9 pub data: *const *const c_char,
10 pub length: usize,
11 pub _capacity: usize,
12}
13
14#[no_mangle]
15pub unsafe extern "C" fn create_workspace(
16 name: *const c_char,
17 path: *const c_char,
18) -> *mut Workspace {
19 assert!(!name.is_null(), "Parameter `name` must not be `null`!");
20 assert!(!path.is_null(), "Parameter `path` must not be `null`!");
21
22 let (name, path) = (
23 std::ffi::CStr::from_ptr(name),
24 std::ffi::CStr::from_ptr(path),
25 );
26 let workspace = Workspace {
27 name: name.to_string_lossy().to_string(),
28 path: path.to_string_lossy().to_string().into(),
29 };
30
31 Box::into_raw(workspace.into())
32}
33
34#[no_mangle]
35pub unsafe extern "C" fn workspace_files(workspace: *const Workspace) -> *mut FileList {
36 assert!(
37 !workspace.is_null(),
38 "Parameter `workspace` must not be `null`!"
39 );
40
41 let files = ManuallyDrop::new(
42 (*workspace)
43 .files()
44 .into_iter()
45 .map(|path| {
46 CString::new(path.to_string_lossy().into_owned())
47 .unwrap()
48 .into_raw() as *const c_char
49 })
50 .collect::<Vec<*const c_char>>(),
51 );
52
53 let file_list = FileList {
54 data: files.as_ptr(),
55 length: files.len(),
56 _capacity: files.capacity(),
57 };
58
59 Box::into_raw(file_list.into())
60}
61
62#[no_mangle]
63pub unsafe extern "C" fn destroy_files(file_list: *mut FileList) {
64 if file_list.is_null() {
65 return;
66 }
67
68 let file_list = Box::from_raw(file_list);
69
70 let file_path_ptrs: Vec<*mut c_char> = Vec::from_raw_parts(
71 file_list.data as *mut *mut c_char,
72 file_list.length,
73 file_list._capacity,
74 );
75
76 for file_path_ptr in file_path_ptrs {
77 drop(CString::from_raw(file_path_ptr));
78 }
79}
80
81#[no_mangle]
82pub unsafe extern "C" fn destroy_workspace(workspace: *mut Workspace) {
83 if workspace.is_null() {
84 return;
85 }
86
87 drop(Box::from_raw(workspace));
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93
94 #[test]
95 fn test_workspace_ffi_boundary() {
96 unsafe {
97 let name = CString::new("test").unwrap().into_raw();
98 let path = CString::new("test/example_workspace/").unwrap().into_raw();
99
100 let workspace = create_workspace(name, path);
101
102 destroy_workspace(workspace);
103
104 drop(Box::from_raw(name));
105 drop(Box::from_raw(path));
106 }
107 }
108
109 #[test]
110 fn test_workspace_files_ffi_boundary() {
111 unsafe {
112 let name = CString::new("test").unwrap().into_raw();
113 let path = CString::new("test/example_workspace/").unwrap().into_raw();
114
115 let workspace = create_workspace(name, path);
116
117 let files = workspace_files(workspace);
118
119 destroy_files(files);
120 destroy_workspace(workspace);
121
122 drop(Box::from_raw(name));
123 drop(Box::from_raw(path));
124 }
125 }
126}