glib/
utils.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use std::{
4    ffi::{OsStr, OsString},
5    mem, ptr,
6};
7
8use crate::{ffi, translate::*, GString};
9
10// rustdoc-stripper-ignore-next
11/// Same as [`get_prgname()`].
12///
13/// [`get_prgname()`]: fn.get_prgname.html
14#[doc(alias = "get_program_name")]
15#[inline]
16pub fn program_name() -> Option<GString> {
17    prgname()
18}
19
20#[doc(alias = "g_get_prgname")]
21#[doc(alias = "get_prgname")]
22#[inline]
23pub fn prgname() -> Option<GString> {
24    unsafe { from_glib_none(ffi::g_get_prgname()) }
25}
26
27// rustdoc-stripper-ignore-next
28/// Same as [`set_prgname()`].
29///
30/// [`set_prgname()`]: fn.set_prgname.html
31#[inline]
32pub fn set_program_name(name: Option<impl IntoGStr>) {
33    set_prgname(name)
34}
35
36#[doc(alias = "g_set_prgname")]
37#[inline]
38pub fn set_prgname(name: Option<impl IntoGStr>) {
39    name.run_with_gstr(|name| unsafe { ffi::g_set_prgname(name.to_glib_none().0) })
40}
41
42#[doc(alias = "g_environ_getenv")]
43pub fn environ_getenv<K: AsRef<OsStr>>(envp: &[OsString], variable: K) -> Option<OsString> {
44    unsafe {
45        from_glib_none(ffi::g_environ_getenv(
46            envp.to_glib_none().0,
47            variable.as_ref().to_glib_none().0,
48        ))
49    }
50}
51
52#[doc(alias = "g_mkstemp")]
53pub fn mkstemp<P: AsRef<std::path::Path>>(tmpl: P) -> i32 {
54    unsafe {
55        // NOTE: This modifies the string in place, which is fine here because
56        // to_glib_none() will create a temporary, NUL-terminated copy of the string.
57        ffi::g_mkstemp(tmpl.as_ref().to_glib_none().0)
58    }
59}
60
61#[doc(alias = "g_mkstemp_full")]
62pub fn mkstemp_full(tmpl: impl AsRef<std::path::Path>, flags: i32, mode: i32) -> i32 {
63    unsafe {
64        // NOTE: This modifies the string in place, which is fine here because
65        // to_glib_none() will create a temporary, NUL-terminated copy of the string.
66        ffi::g_mkstemp_full(tmpl.as_ref().to_glib_none().0, flags, mode)
67    }
68}
69
70#[doc(alias = "g_mkdtemp")]
71pub fn mkdtemp(tmpl: impl AsRef<std::path::Path>) -> Option<std::path::PathBuf> {
72    unsafe {
73        // NOTE: This modifies the string in place and returns it but does not free it
74        // if it returns NULL.
75        let tmpl = tmpl.as_ref().to_glib_full();
76        let res = ffi::g_mkdtemp(tmpl);
77        if res.is_null() {
78            ffi::g_free(tmpl as ffi::gpointer);
79            None
80        } else {
81            from_glib_full(res)
82        }
83    }
84}
85
86#[doc(alias = "g_mkdtemp_full")]
87pub fn mkdtemp_full(tmpl: impl AsRef<std::path::Path>, mode: i32) -> Option<std::path::PathBuf> {
88    unsafe {
89        // NOTE: This modifies the string in place and returns it but does not free it
90        // if it returns NULL.
91        let tmpl = tmpl.as_ref().to_glib_full();
92        let res = ffi::g_mkdtemp_full(tmpl, mode);
93        if res.is_null() {
94            ffi::g_free(tmpl as ffi::gpointer);
95            None
96        } else {
97            from_glib_full(res)
98        }
99    }
100}
101
102#[doc(alias = "g_file_get_contents")]
103pub fn file_get_contents(
104    filename: impl AsRef<std::path::Path>,
105) -> Result<crate::Slice<u8>, crate::Error> {
106    unsafe {
107        let mut contents = ptr::null_mut();
108        let mut length = mem::MaybeUninit::uninit();
109        let mut error = ptr::null_mut();
110        let _ = ffi::g_file_get_contents(
111            filename.as_ref().to_glib_none().0,
112            &mut contents,
113            length.as_mut_ptr(),
114            &mut error,
115        );
116        if error.is_null() {
117            Ok(crate::Slice::from_glib_full_num(
118                contents,
119                length.assume_init() as _,
120            ))
121        } else {
122            Err(from_glib_full(error))
123        }
124    }
125}
126
127pub fn is_canonical_pspec_name(name: &str) -> bool {
128    name.as_bytes().iter().enumerate().all(|(i, c)| {
129        i != 0 && (*c >= b'0' && *c <= b'9' || *c == b'-')
130            || (*c >= b'A' && *c <= b'Z')
131            || (*c >= b'a' && *c <= b'z')
132    })
133}
134
135#[doc(alias = "g_uri_escape_string")]
136pub fn uri_escape_string(
137    unescaped: impl IntoGStr,
138    reserved_chars_allowed: Option<impl IntoGStr>,
139    allow_utf8: bool,
140) -> crate::GString {
141    unescaped.run_with_gstr(|unescaped| {
142        reserved_chars_allowed.run_with_gstr(|reserved_chars_allowed| unsafe {
143            from_glib_full(ffi::g_uri_escape_string(
144                unescaped.to_glib_none().0,
145                reserved_chars_allowed.to_glib_none().0,
146                allow_utf8.into_glib(),
147            ))
148        })
149    })
150}
151
152#[doc(alias = "g_uri_unescape_string")]
153pub fn uri_unescape_string(
154    escaped_string: impl IntoGStr,
155    illegal_characters: Option<impl IntoGStr>,
156) -> Option<crate::GString> {
157    escaped_string.run_with_gstr(|escaped_string| {
158        illegal_characters.run_with_gstr(|illegal_characters| unsafe {
159            from_glib_full(ffi::g_uri_unescape_string(
160                escaped_string.to_glib_none().0,
161                illegal_characters.to_glib_none().0,
162            ))
163        })
164    })
165}
166
167#[doc(alias = "g_uri_parse_scheme")]
168pub fn uri_parse_scheme(uri: impl IntoGStr) -> Option<crate::GString> {
169    uri.run_with_gstr(|uri| unsafe {
170        from_glib_full(ffi::g_uri_parse_scheme(uri.to_glib_none().0))
171    })
172}
173
174#[doc(alias = "g_uri_unescape_segment")]
175pub fn uri_unescape_segment(
176    escaped_string: Option<impl IntoGStr>,
177    escaped_string_end: Option<impl IntoGStr>,
178    illegal_characters: Option<impl IntoGStr>,
179) -> Option<crate::GString> {
180    escaped_string.run_with_gstr(|escaped_string| {
181        escaped_string_end.run_with_gstr(|escaped_string_end| {
182            illegal_characters.run_with_gstr(|illegal_characters| unsafe {
183                from_glib_full(ffi::g_uri_unescape_segment(
184                    escaped_string.to_glib_none().0,
185                    escaped_string_end.to_glib_none().0,
186                    illegal_characters.to_glib_none().0,
187                ))
188            })
189        })
190    })
191}
192
193#[cfg(test)]
194mod tests {
195    use std::{env, sync::Mutex, sync::OnceLock};
196
197    //Mutex to prevent run environment tests parallel
198    fn lock() -> &'static Mutex<()> {
199        static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
200        LOCK.get_or_init(|| Mutex::new(()))
201    }
202
203    const VAR_NAME: &str = "function_environment_test";
204
205    fn check_getenv(val: &str) {
206        let _data = lock().lock().unwrap();
207
208        env::set_var(VAR_NAME, val);
209        assert_eq!(env::var_os(VAR_NAME), Some(val.into()));
210        assert_eq!(crate::getenv(VAR_NAME), Some(val.into()));
211
212        let environ = crate::environ();
213        assert_eq!(crate::environ_getenv(&environ, VAR_NAME), Some(val.into()));
214    }
215
216    fn check_setenv(val: &str) {
217        let _data = lock().lock().unwrap();
218
219        crate::setenv(VAR_NAME, val, true).unwrap();
220        assert_eq!(env::var_os(VAR_NAME), Some(val.into()));
221    }
222
223    #[test]
224    fn getenv() {
225        check_getenv("Test");
226        check_getenv("Тест"); // "Test" in Russian
227    }
228
229    #[test]
230    fn setenv() {
231        check_setenv("Test");
232        check_setenv("Тест"); // "Test" in Russian
233    }
234
235    #[test]
236    fn test_filename_from_uri() {
237        use std::path::PathBuf;
238
239        use crate::GString;
240        let uri: GString = "file:///foo/bar.txt".into();
241        if let Ok((filename, hostname)) = crate::filename_from_uri(&uri) {
242            assert_eq!(filename, PathBuf::from(r"/foo/bar.txt"));
243            assert_eq!(hostname, None);
244        } else {
245            unreachable!();
246        }
247
248        let uri: GString = "file://host/foo/bar.txt".into();
249        if let Ok((filename, hostname)) = crate::filename_from_uri(&uri) {
250            assert_eq!(filename, PathBuf::from(r"/foo/bar.txt"));
251            assert_eq!(hostname, Some(GString::from("host")));
252        } else {
253            unreachable!();
254        }
255    }
256
257    #[test]
258    fn test_uri_parsing() {
259        use crate::GString;
260        assert_eq!(
261            crate::uri_parse_scheme("foo://bar"),
262            Some(GString::from("foo"))
263        );
264        assert_eq!(crate::uri_parse_scheme("foo"), None);
265
266        let escaped = crate::uri_escape_string("&foo", crate::NONE_STR, true);
267        assert_eq!(escaped, GString::from("%26foo"));
268
269        let unescaped = crate::uri_unescape_string(escaped.as_str(), crate::GStr::NONE);
270        assert_eq!(unescaped, Some(GString::from("&foo")));
271
272        assert_eq!(
273            crate::uri_unescape_segment(Some("/foo"), crate::NONE_STR, crate::NONE_STR),
274            Some(GString::from("/foo"))
275        );
276        assert_eq!(
277            crate::uri_unescape_segment(Some("/foo%"), crate::NONE_STR, crate::NONE_STR),
278            None
279        );
280    }
281}