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: *mut i8 = ffi::textdomain(domain: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 = CString::new(domainname).expect(msg:"`domainname` contains an internal 0 byte");
114 unsafe {
115 let result: *mut i8 = ffi::bind_textdomain_codeset(domain:domainname.as_ptr(), codeset:ptr::null());
116 if result.is_null() {
117 let error: 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: String = CStr&str::from_ptr(result)
125 .to_str()
126 .expect(msg:"`bind_textdomain_codeset()` returned non-UTF-8 string")
127 .to_owned();
128 Ok(Some(result))
129 }
130 }
131}
132