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