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}