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 in the current directory.
53///
54/// See the [`mkstemp()`](man:mkstemp(3)) documentation on most UNIX-like systems.
55///
56/// The parameter is a string that should follow the rules for
57/// mkstemp() templates, i.e. contain the string "XXXXXX".
58/// g_mkstemp() is slightly more flexible than mkstemp() in that the
59/// sequence does not have to occur at the very end of the template.
60/// The X string will be modified to form the name of a file that
61/// didn't exist. The string should be in the GLib file name encoding.
62/// Most importantly, on Windows it should be in UTF-8.
63/// ## `tmpl`
64/// template filename
65///
66/// # Returns
67///
68/// A file handle (as from open()) to the file
69///   opened for reading and writing. The file is opened in binary
70///   mode on platforms where there is a difference. The file handle
71///   should be closed with close(). In case of errors, -1 is
72///   returned and `errno` will be set.
73// rustdoc-stripper-ignore-next-stop
74/// Opens a temporary file in the current directory.
75///
76/// See the [`mkstemp()`](man:mkstemp(3)) documentation on most UNIX-like systems.
77///
78/// The parameter is a string that should follow the rules for
79/// mkstemp() templates, i.e. contain the string "XXXXXX".
80/// g_mkstemp() is slightly more flexible than mkstemp() in that the
81/// sequence does not have to occur at the very end of the template.
82/// The X string will be modified to form the name of a file that
83/// didn't exist. The string should be in the GLib file name encoding.
84/// Most importantly, on Windows it should be in UTF-8.
85/// ## `tmpl`
86/// template filename
87///
88/// # Returns
89///
90/// A file handle (as from open()) to the file
91///   opened for reading and writing. The file is opened in binary
92///   mode on platforms where there is a difference. The file handle
93///   should be closed with close(). In case of errors, -1 is
94///   returned and `errno` will be set.
95#[doc(alias = "g_mkstemp")]
96pub fn mkstemp<P: AsRef<std::path::Path>>(tmpl: P) -> i32 {
97    unsafe {
98        // NOTE: This modifies the string in place, which is fine here because
99        // to_glib_none() will create a temporary, NUL-terminated copy of the string.
100        ffi::g_mkstemp(tmpl.as_ref().to_glib_none().0)
101    }
102}
103
104/// Opens a temporary file in the current directory.
105///
106/// See the [`mkstemp()`](man:mkstemp(3)) documentation on most UNIX-like systems.
107///
108/// The parameter is a string that should follow the rules for
109/// mkstemp() templates, i.e. contain the string "XXXXXX".
110/// g_mkstemp_full() is slightly more flexible than mkstemp()
111/// in that the sequence does not have to occur at the very end of the
112/// template and you can pass a @mode and additional @flags. The X
113/// string will be modified to form the name of a file that didn't exist.
114/// The string should be in the GLib file name encoding. Most importantly,
115/// on Windows it should be in UTF-8.
116/// ## `tmpl`
117/// template filename
118/// ## `flags`
119/// flags to pass to an open() call in addition to O_EXCL
120///   and O_CREAT, which are passed automatically
121/// ## `mode`
122/// permissions to create the temporary file with
123///
124/// # Returns
125///
126/// A file handle (as from open()) to the file
127///   opened for reading and writing. The file handle should be
128///   closed with close(). In case of errors, -1 is returned
129///   and `errno` will be set.
130// rustdoc-stripper-ignore-next-stop
131/// Opens a temporary file in the current directory.
132///
133/// See the [`mkstemp()`](man:mkstemp(3)) documentation on most UNIX-like systems.
134///
135/// The parameter is a string that should follow the rules for
136/// mkstemp() templates, i.e. contain the string "XXXXXX".
137/// g_mkstemp_full() is slightly more flexible than mkstemp()
138/// in that the sequence does not have to occur at the very end of the
139/// template and you can pass a @mode and additional @flags. The X
140/// string will be modified to form the name of a file that didn't exist.
141/// The string should be in the GLib file name encoding. Most importantly,
142/// on Windows it should be in UTF-8.
143/// ## `tmpl`
144/// template filename
145/// ## `flags`
146/// flags to pass to an open() call in addition to O_EXCL
147///   and O_CREAT, which are passed automatically
148/// ## `mode`
149/// permissions to create the temporary file with
150///
151/// # Returns
152///
153/// A file handle (as from open()) to the file
154///   opened for reading and writing. The file handle should be
155///   closed with close(). In case of errors, -1 is returned
156///   and `errno` will be set.
157#[doc(alias = "g_mkstemp_full")]
158pub fn mkstemp_full(tmpl: impl AsRef<std::path::Path>, flags: i32, mode: i32) -> i32 {
159    unsafe {
160        // NOTE: This modifies the string in place, which is fine here because
161        // to_glib_none() will create a temporary, NUL-terminated copy of the string.
162        ffi::g_mkstemp_full(tmpl.as_ref().to_glib_none().0, flags, mode)
163    }
164}
165
166/// Creates a temporary directory in the current directory.
167///
168/// See the [`mkdtemp()`](man:mkdtemp(3)) documentation on most UNIX-like systems.
169///
170/// The parameter is a string that should follow the rules for
171/// mkdtemp() templates, i.e. contain the string "XXXXXX".
172/// g_mkdtemp() is slightly more flexible than mkdtemp() in that the
173/// sequence does not have to occur at the very end of the template.
174/// The X string will be modified to form the name of a directory that
175/// didn't exist.
176/// The string should be in the GLib file name encoding. Most importantly,
177/// on Windows it should be in UTF-8.
178///
179/// If you are going to be creating a temporary directory inside the
180/// directory returned by g_get_tmp_dir(), you might want to use
181/// g_dir_make_tmp() instead.
182/// ## `tmpl`
183/// template directory name
184///
185/// # Returns
186///
187/// A pointer to @tmpl, which has been
188///   modified to hold the directory name.  In case of errors, [`None`] is
189///   returned and `errno` will be set.
190// rustdoc-stripper-ignore-next-stop
191/// Creates a temporary directory in the current directory.
192///
193/// See the [`mkdtemp()`](man:mkdtemp(3)) documentation on most UNIX-like systems.
194///
195/// The parameter is a string that should follow the rules for
196/// mkdtemp() templates, i.e. contain the string "XXXXXX".
197/// g_mkdtemp() is slightly more flexible than mkdtemp() in that the
198/// sequence does not have to occur at the very end of the template.
199/// The X string will be modified to form the name of a directory that
200/// didn't exist.
201/// The string should be in the GLib file name encoding. Most importantly,
202/// on Windows it should be in UTF-8.
203///
204/// If you are going to be creating a temporary directory inside the
205/// directory returned by g_get_tmp_dir(), you might want to use
206/// g_dir_make_tmp() instead.
207/// ## `tmpl`
208/// template directory name
209///
210/// # Returns
211///
212/// A pointer to @tmpl, which has been
213///   modified to hold the directory name.  In case of errors, [`None`] is
214///   returned and `errno` will be set.
215#[doc(alias = "g_mkdtemp")]
216pub fn mkdtemp(tmpl: impl AsRef<std::path::Path>) -> Option<std::path::PathBuf> {
217    unsafe {
218        // NOTE: This modifies the string in place and returns it but does not free it
219        // if it returns NULL.
220        let tmpl = tmpl.as_ref().to_glib_full();
221        let res = ffi::g_mkdtemp(tmpl);
222        if res.is_null() {
223            ffi::g_free(tmpl as ffi::gpointer);
224            None
225        } else {
226            from_glib_full(res)
227        }
228    }
229}
230
231/// Creates a temporary directory in the current directory.
232///
233/// See the [`mkdtemp()`](man:mkdtemp(3)) documentation on most UNIX-like systems.
234///
235/// The parameter is a string that should follow the rules for
236/// mkdtemp() templates, i.e. contain the string "XXXXXX".
237/// g_mkdtemp_full() is slightly more flexible than mkdtemp() in that the
238/// sequence does not have to occur at the very end of the template
239/// and you can pass a @mode. The X string will be modified to form
240/// the name of a directory that didn't exist. The string should be
241/// in the GLib file name encoding. Most importantly, on Windows it
242/// should be in UTF-8.
243///
244/// If you are going to be creating a temporary directory inside the
245/// directory returned by g_get_tmp_dir(), you might want to use
246/// g_dir_make_tmp() instead.
247/// ## `tmpl`
248/// template directory name
249/// ## `mode`
250/// permissions to create the temporary directory with
251///
252/// # Returns
253///
254/// A pointer to @tmpl, which has been
255///   modified to hold the directory name. In case of errors, [`None`] is
256///   returned, and `errno` will be set.
257// rustdoc-stripper-ignore-next-stop
258/// Creates a temporary directory in the current directory.
259///
260/// See the [`mkdtemp()`](man:mkdtemp(3)) documentation on most UNIX-like systems.
261///
262/// The parameter is a string that should follow the rules for
263/// mkdtemp() templates, i.e. contain the string "XXXXXX".
264/// g_mkdtemp_full() is slightly more flexible than mkdtemp() in that the
265/// sequence does not have to occur at the very end of the template
266/// and you can pass a @mode. The X string will be modified to form
267/// the name of a directory that didn't exist. The string should be
268/// in the GLib file name encoding. Most importantly, on Windows it
269/// should be in UTF-8.
270///
271/// If you are going to be creating a temporary directory inside the
272/// directory returned by g_get_tmp_dir(), you might want to use
273/// g_dir_make_tmp() instead.
274/// ## `tmpl`
275/// template directory name
276/// ## `mode`
277/// permissions to create the temporary directory with
278///
279/// # Returns
280///
281/// A pointer to @tmpl, which has been
282///   modified to hold the directory name. In case of errors, [`None`] is
283///   returned, and `errno` will be set.
284#[doc(alias = "g_mkdtemp_full")]
285pub fn mkdtemp_full(tmpl: impl AsRef<std::path::Path>, mode: i32) -> Option<std::path::PathBuf> {
286    unsafe {
287        // NOTE: This modifies the string in place and returns it but does not free it
288        // if it returns NULL.
289        let tmpl = tmpl.as_ref().to_glib_full();
290        let res = ffi::g_mkdtemp_full(tmpl, mode);
291        if res.is_null() {
292            ffi::g_free(tmpl as ffi::gpointer);
293            None
294        } else {
295            from_glib_full(res)
296        }
297    }
298}
299
300/// Reads an entire file into allocated memory, with good error
301/// checking.
302///
303/// If the call was successful, it returns [`true`] and sets @contents to the file
304/// contents and @length to the length of the file contents in bytes. The string
305/// stored in @contents will be nul-terminated, so for text files you can pass
306/// [`None`] for the @length argument. If the call was not successful, it returns
307/// [`false`] and sets @error. The error domain is `G_FILE_ERROR`. Possible error
308/// codes are those in the #GFileError enumeration. In the error case,
309/// @contents is set to [`None`] and @length is set to zero.
310/// ## `filename`
311/// name of a file to read contents from, in the GLib file name encoding
312///
313/// # Returns
314///
315/// [`true`] on success, [`false`] if an error occurred
316///
317/// ## `contents`
318/// location to store an allocated string, use g_free() to free
319///     the returned string
320// rustdoc-stripper-ignore-next-stop
321/// Reads an entire file into allocated memory, with good error
322/// checking.
323///
324/// If the call was successful, it returns [`true`] and sets @contents to the file
325/// contents and @length to the length of the file contents in bytes. The string
326/// stored in @contents will be nul-terminated, so for text files you can pass
327/// [`None`] for the @length argument. If the call was not successful, it returns
328/// [`false`] and sets @error. The error domain is `G_FILE_ERROR`. Possible error
329/// codes are those in the #GFileError enumeration. In the error case,
330/// @contents is set to [`None`] and @length is set to zero.
331/// ## `filename`
332/// name of a file to read contents from, in the GLib file name encoding
333///
334/// # Returns
335///
336/// [`true`] on success, [`false`] if an error occurred
337///
338/// ## `contents`
339/// location to store an allocated string, use g_free() to free
340///     the returned string
341#[doc(alias = "g_file_get_contents")]
342pub fn file_get_contents(
343    filename: impl AsRef<std::path::Path>,
344) -> Result<crate::Slice<u8>, crate::Error> {
345    unsafe {
346        let mut contents = ptr::null_mut();
347        let mut length = mem::MaybeUninit::uninit();
348        let mut error = ptr::null_mut();
349        let _ = ffi::g_file_get_contents(
350            filename.as_ref().to_glib_none().0,
351            &mut contents,
352            length.as_mut_ptr(),
353            &mut error,
354        );
355        if error.is_null() {
356            Ok(crate::Slice::from_glib_full_num(
357                contents,
358                length.assume_init() as _,
359            ))
360        } else {
361            Err(from_glib_full(error))
362        }
363    }
364}
365
366pub fn is_canonical_pspec_name(name: &str) -> bool {
367    name.as_bytes().iter().enumerate().all(|(i, c)| {
368        i != 0 && (*c >= b'0' && *c <= b'9' || *c == b'-')
369            || (*c >= b'A' && *c <= b'Z')
370            || (*c >= b'a' && *c <= b'z')
371    })
372}
373
374#[doc(alias = "g_uri_escape_string")]
375pub fn uri_escape_string(
376    unescaped: impl IntoGStr,
377    reserved_chars_allowed: Option<impl IntoGStr>,
378    allow_utf8: bool,
379) -> crate::GString {
380    unescaped.run_with_gstr(|unescaped| {
381        reserved_chars_allowed.run_with_gstr(|reserved_chars_allowed| unsafe {
382            from_glib_full(ffi::g_uri_escape_string(
383                unescaped.to_glib_none().0,
384                reserved_chars_allowed.to_glib_none().0,
385                allow_utf8.into_glib(),
386            ))
387        })
388    })
389}
390
391#[doc(alias = "g_uri_unescape_string")]
392pub fn uri_unescape_string(
393    escaped_string: impl IntoGStr,
394    illegal_characters: Option<impl IntoGStr>,
395) -> Option<crate::GString> {
396    escaped_string.run_with_gstr(|escaped_string| {
397        illegal_characters.run_with_gstr(|illegal_characters| unsafe {
398            from_glib_full(ffi::g_uri_unescape_string(
399                escaped_string.to_glib_none().0,
400                illegal_characters.to_glib_none().0,
401            ))
402        })
403    })
404}
405
406#[doc(alias = "g_uri_parse_scheme")]
407pub fn uri_parse_scheme(uri: impl IntoGStr) -> Option<crate::GString> {
408    uri.run_with_gstr(|uri| unsafe {
409        from_glib_full(ffi::g_uri_parse_scheme(uri.to_glib_none().0))
410    })
411}
412
413#[doc(alias = "g_uri_unescape_segment")]
414pub fn uri_unescape_segment(
415    escaped_string: Option<impl IntoGStr>,
416    escaped_string_end: Option<impl IntoGStr>,
417    illegal_characters: Option<impl IntoGStr>,
418) -> Option<crate::GString> {
419    escaped_string.run_with_gstr(|escaped_string| {
420        escaped_string_end.run_with_gstr(|escaped_string_end| {
421            illegal_characters.run_with_gstr(|illegal_characters| unsafe {
422                from_glib_full(ffi::g_uri_unescape_segment(
423                    escaped_string.to_glib_none().0,
424                    escaped_string_end.to_glib_none().0,
425                    illegal_characters.to_glib_none().0,
426                ))
427            })
428        })
429    })
430}
431
432#[cfg(test)]
433mod tests {
434    use std::{env, sync::Mutex, sync::OnceLock};
435
436    //Mutex to prevent run environment tests parallel
437    fn lock() -> &'static Mutex<()> {
438        static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
439        LOCK.get_or_init(|| Mutex::new(()))
440    }
441
442    const VAR_NAME: &str = "function_environment_test";
443
444    fn check_getenv(val: &str) {
445        let _data = lock().lock().unwrap();
446
447        env::set_var(VAR_NAME, val);
448        assert_eq!(env::var_os(VAR_NAME), Some(val.into()));
449        assert_eq!(crate::getenv(VAR_NAME), Some(val.into()));
450
451        let environ = crate::environ();
452        assert_eq!(crate::environ_getenv(&environ, VAR_NAME), Some(val.into()));
453    }
454
455    fn check_setenv(val: &str) {
456        let _data = lock().lock().unwrap();
457
458        crate::setenv(VAR_NAME, val, true).unwrap();
459        assert_eq!(env::var_os(VAR_NAME), Some(val.into()));
460    }
461
462    #[test]
463    fn getenv() {
464        check_getenv("Test");
465        check_getenv("Тест"); // "Test" in Russian
466    }
467
468    #[test]
469    fn setenv() {
470        check_setenv("Test");
471        check_setenv("Тест"); // "Test" in Russian
472    }
473
474    #[test]
475    fn test_filename_from_uri() {
476        use std::path::PathBuf;
477
478        use crate::GString;
479        let uri: GString = "file:///foo/bar.txt".into();
480        if let Ok((filename, hostname)) = crate::filename_from_uri(&uri) {
481            assert_eq!(filename, PathBuf::from(r"/foo/bar.txt"));
482            assert_eq!(hostname, None);
483        } else {
484            unreachable!();
485        }
486
487        let uri: GString = "file://host/foo/bar.txt".into();
488        if let Ok((filename, hostname)) = crate::filename_from_uri(&uri) {
489            assert_eq!(filename, PathBuf::from(r"/foo/bar.txt"));
490            assert_eq!(hostname, Some(GString::from("host")));
491        } else {
492            unreachable!();
493        }
494    }
495
496    #[test]
497    fn test_uri_parsing() {
498        use crate::GString;
499        assert_eq!(
500            crate::uri_parse_scheme("foo://bar"),
501            Some(GString::from("foo"))
502        );
503        assert_eq!(crate::uri_parse_scheme("foo"), None);
504
505        let escaped = crate::uri_escape_string("&foo", crate::NONE_STR, true);
506        assert_eq!(escaped, GString::from("%26foo"));
507
508        let unescaped = crate::uri_unescape_string(escaped.as_str(), crate::GStr::NONE);
509        assert_eq!(unescaped, Some(GString::from("&foo")));
510
511        assert_eq!(
512            crate::uri_unescape_segment(Some("/foo"), crate::NONE_STR, crate::NONE_STR),
513            Some(GString::from("/foo"))
514        );
515        assert_eq!(
516            crate::uri_unescape_segment(Some("/foo%"), crate::NONE_STR, crate::NONE_STR),
517            None
518        );
519    }
520}