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}