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/// Opens a temporary file. See the mkstemp() documentation
53/// on most UNIX-like systems.
54///
55/// The parameter is a string that should follow the rules for
56/// mkstemp() templates, i.e. contain the string "XXXXXX".
57/// g_mkstemp() is slightly more flexible than mkstemp() in that the
58/// sequence does not have to occur at the very end of the template.
59/// The X string will be modified to form the name of a file that
60/// didn't exist. The string should be in the GLib file name encoding.
61/// Most importantly, on Windows it should be in UTF-8.
62/// ## `tmpl`
63/// template filename
64///
65/// # Returns
66///
67/// A file handle (as from open()) to the file
68///   opened for reading and writing. The file is opened in binary
69///   mode on platforms where there is a difference. The file handle
70///   should be closed with close(). In case of errors, -1 is
71///   returned and `errno` will be set.
72#[doc(alias = "g_mkstemp")]
73pub fn mkstemp<P: AsRef<std::path::Path>>(tmpl: P) -> i32 {
74    unsafe {
75        // NOTE: This modifies the string in place, which is fine here because
76        // to_glib_none() will create a temporary, NUL-terminated copy of the string.
77        ffi::g_mkstemp(tmpl.as_ref().to_glib_none().0)
78    }
79}
80
81/// Opens a temporary file. See the mkstemp() documentation
82/// on most UNIX-like systems.
83///
84/// The parameter is a string that should follow the rules for
85/// mkstemp() templates, i.e. contain the string "XXXXXX".
86/// g_mkstemp_full() is slightly more flexible than mkstemp()
87/// in that the sequence does not have to occur at the very end of the
88/// template and you can pass a @mode and additional @flags. The X
89/// string will be modified to form the name of a file that didn't exist.
90/// The string should be in the GLib file name encoding. Most importantly,
91/// on Windows it should be in UTF-8.
92/// ## `tmpl`
93/// template filename
94/// ## `flags`
95/// flags to pass to an open() call in addition to O_EXCL
96///   and O_CREAT, which are passed automatically
97/// ## `mode`
98/// permissions to create the temporary file with
99///
100/// # Returns
101///
102/// A file handle (as from open()) to the file
103///   opened for reading and writing. The file handle should be
104///   closed with close(). In case of errors, -1 is returned
105///   and `errno` will be set.
106#[doc(alias = "g_mkstemp_full")]
107pub fn mkstemp_full(tmpl: impl AsRef<std::path::Path>, flags: i32, mode: i32) -> i32 {
108    unsafe {
109        // NOTE: This modifies the string in place, which is fine here because
110        // to_glib_none() will create a temporary, NUL-terminated copy of the string.
111        ffi::g_mkstemp_full(tmpl.as_ref().to_glib_none().0, flags, mode)
112    }
113}
114
115/// Creates a temporary directory. See the mkdtemp() documentation
116/// on most UNIX-like systems.
117///
118/// The parameter is a string that should follow the rules for
119/// mkdtemp() templates, i.e. contain the string "XXXXXX".
120/// g_mkdtemp() is slightly more flexible than mkdtemp() in that the
121/// sequence does not have to occur at the very end of the template.
122/// The X string will be modified to form the name of a directory that
123/// didn't exist.
124/// The string should be in the GLib file name encoding. Most importantly,
125/// on Windows it should be in UTF-8.
126///
127/// If you are going to be creating a temporary directory inside the
128/// directory returned by g_get_tmp_dir(), you might want to use
129/// g_dir_make_tmp() instead.
130/// ## `tmpl`
131/// template directory name
132///
133/// # Returns
134///
135/// A pointer to @tmpl, which has been
136///   modified to hold the directory name.  In case of errors, [`None`] is
137///   returned and `errno` will be set.
138#[doc(alias = "g_mkdtemp")]
139pub fn mkdtemp(tmpl: impl AsRef<std::path::Path>) -> Option<std::path::PathBuf> {
140    unsafe {
141        // NOTE: This modifies the string in place and returns it but does not free it
142        // if it returns NULL.
143        let tmpl = tmpl.as_ref().to_glib_full();
144        let res = ffi::g_mkdtemp(tmpl);
145        if res.is_null() {
146            ffi::g_free(tmpl as ffi::gpointer);
147            None
148        } else {
149            from_glib_full(res)
150        }
151    }
152}
153
154/// Creates a temporary directory. See the mkdtemp() documentation
155/// on most UNIX-like systems.
156///
157/// The parameter is a string that should follow the rules for
158/// mkdtemp() templates, i.e. contain the string "XXXXXX".
159/// g_mkdtemp_full() is slightly more flexible than mkdtemp() in that the
160/// sequence does not have to occur at the very end of the template
161/// and you can pass a @mode. The X string will be modified to form
162/// the name of a directory that didn't exist. The string should be
163/// in the GLib file name encoding. Most importantly, on Windows it
164/// should be in UTF-8.
165///
166/// If you are going to be creating a temporary directory inside the
167/// directory returned by g_get_tmp_dir(), you might want to use
168/// g_dir_make_tmp() instead.
169/// ## `tmpl`
170/// template directory name
171/// ## `mode`
172/// permissions to create the temporary directory with
173///
174/// # Returns
175///
176/// A pointer to @tmpl, which has been
177///   modified to hold the directory name. In case of errors, [`None`] is
178///   returned, and `errno` will be set.
179#[doc(alias = "g_mkdtemp_full")]
180pub fn mkdtemp_full(tmpl: impl AsRef<std::path::Path>, mode: i32) -> Option<std::path::PathBuf> {
181    unsafe {
182        // NOTE: This modifies the string in place and returns it but does not free it
183        // if it returns NULL.
184        let tmpl = tmpl.as_ref().to_glib_full();
185        let res = ffi::g_mkdtemp_full(tmpl, mode);
186        if res.is_null() {
187            ffi::g_free(tmpl as ffi::gpointer);
188            None
189        } else {
190            from_glib_full(res)
191        }
192    }
193}
194
195#[doc(alias = "g_file_get_contents")]
196pub fn file_get_contents(
197    filename: impl AsRef<std::path::Path>,
198) -> Result<crate::Slice<u8>, crate::Error> {
199    unsafe {
200        let mut contents = ptr::null_mut();
201        let mut length = mem::MaybeUninit::uninit();
202        let mut error = ptr::null_mut();
203        let _ = ffi::g_file_get_contents(
204            filename.as_ref().to_glib_none().0,
205            &mut contents,
206            length.as_mut_ptr(),
207            &mut error,
208        );
209        if error.is_null() {
210            Ok(crate::Slice::from_glib_full_num(
211                contents,
212                length.assume_init() as _,
213            ))
214        } else {
215            Err(from_glib_full(error))
216        }
217    }
218}
219
220pub fn is_canonical_pspec_name(name: &str) -> bool {
221    name.as_bytes().iter().enumerate().all(|(i, c)| {
222        i != 0 && (*c >= b'0' && *c <= b'9' || *c == b'-')
223            || (*c >= b'A' && *c <= b'Z')
224            || (*c >= b'a' && *c <= b'z')
225    })
226}
227
228#[doc(alias = "g_uri_escape_string")]
229pub fn uri_escape_string(
230    unescaped: impl IntoGStr,
231    reserved_chars_allowed: Option<impl IntoGStr>,
232    allow_utf8: bool,
233) -> crate::GString {
234    unescaped.run_with_gstr(|unescaped| {
235        reserved_chars_allowed.run_with_gstr(|reserved_chars_allowed| unsafe {
236            from_glib_full(ffi::g_uri_escape_string(
237                unescaped.to_glib_none().0,
238                reserved_chars_allowed.to_glib_none().0,
239                allow_utf8.into_glib(),
240            ))
241        })
242    })
243}
244
245#[doc(alias = "g_uri_unescape_string")]
246pub fn uri_unescape_string(
247    escaped_string: impl IntoGStr,
248    illegal_characters: Option<impl IntoGStr>,
249) -> Option<crate::GString> {
250    escaped_string.run_with_gstr(|escaped_string| {
251        illegal_characters.run_with_gstr(|illegal_characters| unsafe {
252            from_glib_full(ffi::g_uri_unescape_string(
253                escaped_string.to_glib_none().0,
254                illegal_characters.to_glib_none().0,
255            ))
256        })
257    })
258}
259
260#[doc(alias = "g_uri_parse_scheme")]
261pub fn uri_parse_scheme(uri: impl IntoGStr) -> Option<crate::GString> {
262    uri.run_with_gstr(|uri| unsafe {
263        from_glib_full(ffi::g_uri_parse_scheme(uri.to_glib_none().0))
264    })
265}
266
267#[doc(alias = "g_uri_unescape_segment")]
268pub fn uri_unescape_segment(
269    escaped_string: Option<impl IntoGStr>,
270    escaped_string_end: Option<impl IntoGStr>,
271    illegal_characters: Option<impl IntoGStr>,
272) -> Option<crate::GString> {
273    escaped_string.run_with_gstr(|escaped_string| {
274        escaped_string_end.run_with_gstr(|escaped_string_end| {
275            illegal_characters.run_with_gstr(|illegal_characters| unsafe {
276                from_glib_full(ffi::g_uri_unescape_segment(
277                    escaped_string.to_glib_none().0,
278                    escaped_string_end.to_glib_none().0,
279                    illegal_characters.to_glib_none().0,
280                ))
281            })
282        })
283    })
284}
285
286#[cfg(test)]
287mod tests {
288    use std::{env, sync::Mutex, sync::OnceLock};
289
290    //Mutex to prevent run environment tests parallel
291    fn lock() -> &'static Mutex<()> {
292        static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
293        LOCK.get_or_init(|| Mutex::new(()))
294    }
295
296    const VAR_NAME: &str = "function_environment_test";
297
298    fn check_getenv(val: &str) {
299        let _data = lock().lock().unwrap();
300
301        env::set_var(VAR_NAME, val);
302        assert_eq!(env::var_os(VAR_NAME), Some(val.into()));
303        assert_eq!(crate::getenv(VAR_NAME), Some(val.into()));
304
305        let environ = crate::environ();
306        assert_eq!(crate::environ_getenv(&environ, VAR_NAME), Some(val.into()));
307    }
308
309    fn check_setenv(val: &str) {
310        let _data = lock().lock().unwrap();
311
312        crate::setenv(VAR_NAME, val, true).unwrap();
313        assert_eq!(env::var_os(VAR_NAME), Some(val.into()));
314    }
315
316    #[test]
317    fn getenv() {
318        check_getenv("Test");
319        check_getenv("Тест"); // "Test" in Russian
320    }
321
322    #[test]
323    fn setenv() {
324        check_setenv("Test");
325        check_setenv("Тест"); // "Test" in Russian
326    }
327
328    #[test]
329    fn test_filename_from_uri() {
330        use std::path::PathBuf;
331
332        use crate::GString;
333        let uri: GString = "file:///foo/bar.txt".into();
334        if let Ok((filename, hostname)) = crate::filename_from_uri(&uri) {
335            assert_eq!(filename, PathBuf::from(r"/foo/bar.txt"));
336            assert_eq!(hostname, None);
337        } else {
338            unreachable!();
339        }
340
341        let uri: GString = "file://host/foo/bar.txt".into();
342        if let Ok((filename, hostname)) = crate::filename_from_uri(&uri) {
343            assert_eq!(filename, PathBuf::from(r"/foo/bar.txt"));
344            assert_eq!(hostname, Some(GString::from("host")));
345        } else {
346            unreachable!();
347        }
348    }
349
350    #[test]
351    fn test_uri_parsing() {
352        use crate::GString;
353        assert_eq!(
354            crate::uri_parse_scheme("foo://bar"),
355            Some(GString::from("foo"))
356        );
357        assert_eq!(crate::uri_parse_scheme("foo"), None);
358
359        let escaped = crate::uri_escape_string("&foo", crate::NONE_STR, true);
360        assert_eq!(escaped, GString::from("%26foo"));
361
362        let unescaped = crate::uri_unescape_string(escaped.as_str(), crate::GStr::NONE);
363        assert_eq!(unescaped, Some(GString::from("&foo")));
364
365        assert_eq!(
366            crate::uri_unescape_segment(Some("/foo"), crate::NONE_STR, crate::NONE_STR),
367            Some(GString::from("/foo"))
368        );
369        assert_eq!(
370            crate::uri_unescape_segment(Some("/foo%"), crate::NONE_STR, crate::NONE_STR),
371            None
372        );
373    }
374}