gettextrs/
getters.rs

1//! Query gettext configuration.
2//!
3//! There are just a few settings in gettext. The only required one is the message domain, set
4//! using [`textdomain`][::textdomain]; the other two are the path where translations are searched
5//! for, and the encoding to which the messages should be converted.
6//!
7//! The underlying C API uses the same functions both as setters and as getters: to get the current
8//! value, you just pass `NULL` as an argument. This is ergonomic in C, but not in Rust: wrapping
9//! everything in `Option`s is a tad ugly. That's why this crate provides getters as separate
10//! functions. They're in a module of their own to prevent them from clashing with any functions
11//! that the underlying C API might gain in the future.
12
13extern crate gettext_sys as ffi;
14
15use std::ffi::{CStr, CString};
16use std::io;
17use std::path::PathBuf;
18use std::ptr;
19
20/// Get currently set message domain.
21///
22/// If you want to *set* the domain, rather than getting its current value, use
23/// [`textdomain`][::textdomain].
24///
25/// For more information, see [textdomain(3)][].
26///
27/// [textdomain(3)]: https://www.man7.org/linux/man-pages/man3/textdomain.3.html
28pub fn current_textdomain() -> Result<Vec<u8>, io::Error> {
29    unsafe {
30        let result = ffi::textdomain(ptr::null());
31        if result.is_null() {
32            Err(io::Error::last_os_error())
33        } else {
34            Ok(CStr::from_ptr(result).to_bytes().to_owned())
35        }
36    }
37}
38
39/// Get base directory for the given domain.
40///
41/// If you want to *set* the directory, rather than querying its current value, use
42/// [`bindtextdomain`][::bindtextdomain].
43///
44/// For more information, see [bindtextdomain(3)][].
45///
46/// [bindtextdomain(3)]: https://www.man7.org/linux/man-pages/man3/bindtextdomain.3.html
47///
48/// # Panics
49///
50/// Panics if `domainname` contains an internal 0 byte, as such values can't be passed to the
51/// underlying C API.
52pub fn domain_directory<T: Into<Vec<u8>>>(domainname: T) -> Result<PathBuf, io::Error> {
53    let domainname = CString::new(domainname).expect("`domainname` contains an internal 0 byte");
54
55    #[cfg(windows)]
56    {
57        use std::ffi::OsString;
58        use std::os::windows::ffi::OsStringExt;
59
60        unsafe {
61            let mut ptr = ffi::wbindtextdomain(domainname.as_ptr(), ptr::null());
62            if ptr.is_null() {
63                Err(io::Error::last_os_error())
64            } else {
65                let mut result = vec![];
66                while *ptr != 0_u16 {
67                    result.push(*ptr);
68                    ptr = ptr.offset(1);
69                }
70                Ok(PathBuf::from(OsString::from_wide(&result)))
71            }
72        }
73    }
74
75    #[cfg(not(windows))]
76    {
77        use std::ffi::OsString;
78        use std::os::unix::ffi::OsStringExt;
79
80        unsafe {
81            let result = ffi::bindtextdomain(domainname.as_ptr(), ptr::null());
82            if result.is_null() {
83                Err(io::Error::last_os_error())
84            } else {
85                let result = CStr::from_ptr(result);
86                Ok(PathBuf::from(OsString::from_vec(
87                    result.to_bytes().to_vec(),
88                )))
89            }
90        }
91    }
92}
93
94/// Get encoding of translated messages for given domain.
95///
96/// Returns `None` if encoding is not set.
97///
98/// If you want to *set* an encoding, rather than get the current one, use
99/// [`bind_textdomain_codeset`][::bind_textdomain_codeset].
100///
101/// For more information, see [bind_textdomain_codeset(3)][].
102///
103/// [bind_textdomain_codeset(3)]: https://www.man7.org/linux/man-pages/man3/bind_textdomain_codeset.3.html
104///
105/// # Panics
106///
107/// Panics if:
108/// * `domainname` contains an internal 0 byte, as such values can't be passed to the underlying
109///     C API;
110/// * the result is not in UTF-8 (which shouldn't happen as the results should always be ASCII, as
111///     they're just codeset names).
112pub fn textdomain_codeset<T: Into<Vec<u8>>>(domainname: T) -> Result<Option<String>, io::Error> {
113    let domainname = CString::new(domainname).expect("`domainname` contains an internal 0 byte");
114    unsafe {
115        let result = ffi::bind_textdomain_codeset(domainname.as_ptr(), ptr::null());
116        if result.is_null() {
117            let error = io::Error::last_os_error();
118            if let Some(0) = error.raw_os_error() {
119                return Ok(None);
120            } else {
121                return Err(error);
122            }
123        } else {
124            let result = CStr::from_ptr(result)
125                .to_str()
126                .expect("`bind_textdomain_codeset()` returned non-UTF-8 string")
127                .to_owned();
128            Ok(Some(result))
129        }
130    }
131}