unixcli/
err.rs

1//
2// Copyright (c) 2017 KAMADA Ken'ichi.
3// All rights reserved.
4//
5// Redistribution and use in source and binary forms, with or without
6// modification, are permitted provided that the following conditions
7// are met:
8// 1. Redistributions of source code must retain the above copyright
9//    notice, this list of conditions and the following disclaimer.
10// 2. Redistributions in binary form must reproduce the above copyright
11//    notice, this list of conditions and the following disclaimer in the
12//    documentation and/or other materials provided with the distribution.
13//
14// THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17// ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24// SUCH DAMAGE.
25//
26
27//! Prints messages to the standard error output.
28
29use std::fmt;
30use std::io::Write;
31#[cfg(unix)]
32use std::os::unix::ffi::OsStrExt;
33use std::path::Path;
34use std::str;
35
36use progname;
37
38/// Prints the formatted message to the standard error output (stderr)
39/// and terminates the program with the given `status` value.
40/// The program name, a colon, and a space are output before the message,
41/// and a newline character follows.
42#[macro_export]
43macro_rules! err {
44    ($status:expr, $fmt:expr) => (
45        $crate::err::verrp($status, None as Option<&str>,
46                           format_args!(concat!($fmt, "\n")));
47    );
48    ($status:expr, $fmt:expr, $($args:tt)*) => (
49        $crate::err::verrp($status, None as Option<&str>,
50                           format_args!(concat!($fmt, "\n"), $($args)*));
51    );
52}
53
54/// Prints the formatted message to the standard error output (stderr)
55/// and terminates the program with the given `status` value.
56/// The program name, a colon, a pathname, another colon, and a space
57/// are output before the message, and a newline character follows.
58#[macro_export]
59macro_rules! errp {
60    ($status:expr, $path:expr, $fmt:expr) => (
61        $crate::err::verrp($status, Some($path),
62                           format_args!(concat!($fmt, "\n")));
63    );
64    ($status:expr, $path:expr, $fmt:expr, $($args:tt)*) => (
65        $crate::err::verrp($status, Some($path),
66                           format_args!(concat!($fmt, "\n"), $($args)*));
67    );
68}
69
70/// Prints the formatted message to the standard error output (stderr).
71/// The program name, a colon, and a space are output before the message,
72/// and a newline character follows.
73#[macro_export]
74macro_rules! warn {
75    ($fmt:expr) => (
76        $crate::err::vwarnp(None as Option<&str>,
77                            format_args!(concat!($fmt, "\n")));
78    );
79    ($fmt:expr, $($args:tt)*) => (
80        $crate::err::vwarnp(None as Option<&str>,
81                            format_args!(concat!($fmt, "\n"), $($args)*));
82    );
83}
84
85/// Prints the formatted message to the standard error output (stderr).
86/// The program name, a colon, a pathname, another colon, and a space
87/// are output before the message, and a newline character follows.
88#[macro_export]
89macro_rules! warnp {
90    ($path:expr, $fmt:expr) => (
91        $crate::err::vwarnp(Some($path),
92                            format_args!(concat!($fmt, "\n")));
93    );
94    ($path:expr, $fmt:expr, $($args:tt)*) => (
95        $crate::err::vwarnp(Some($path),
96                            format_args!(concat!($fmt, "\n"), $($args)*));
97    );
98}
99
100/// This function is not a part of public/stable APIs.
101/// This function should be used through the `err!` or `errp!` macros.
102pub fn verrp<P>(status: i32, path: Option<P>, fmt: fmt::Arguments) -> ! where P: AsRef<Path> {
103    vwarnp(path, fmt);
104    tester::exit(status);
105}
106
107/// This function is not a part of public/stable APIs.
108/// This function should be used through the `warn!` or `warnp!` macros.
109pub fn vwarnp<P>(path: Option<P>, fmt: fmt::Arguments) where P: AsRef<Path> {
110    let mut buf = Vec::new();
111    if let Some(ref os) = *progname::getprogname_arc() {
112        #[cfg(unix)]
113        buf.extend_from_slice(os.as_bytes());
114        #[cfg(not(unix))]
115        match os.to_str() {
116            Some(s) => { let _ = write!(&mut buf, "{}", s); },
117            None => {},
118        };
119    }
120    buf.extend_from_slice(b": ");
121    if let Some(path) = path {
122        #[cfg(unix)]
123        buf.extend_from_slice(path.as_ref().as_os_str().as_bytes());
124        #[cfg(not(unix))]
125        match path.as_ref().to_str() {
126            Some(s) => { let _ = write!(&mut buf, "{}", s); },
127            None => {},
128        };
129        buf.extend_from_slice(b": ");
130    }
131    let msgstart = buf.len();
132    let _ = buf.write_fmt(fmt);
133    if let Err(e) = tester::stderr().write(&buf) {
134        // The message was composed by write_fmt, so from_utf8 should not fail.
135        let msg = str::from_utf8(&buf[msgstart..]).unwrap_or("");
136        // If writing to stderr failed, writing the panic message will
137        // also fail, but anyway...
138        panic!("failed to write to stderr: {}: {}", e, msg);
139    }
140}
141
142#[cfg(not(test))]
143mod tester {
144    #[inline(always)]
145    pub fn exit(status: i32) -> ! { ::std::process::exit(status); }
146    #[inline(always)]
147    pub fn stderr() -> ::std::io::Stderr { ::std::io::stderr() }
148}
149
150#[cfg(test)]
151mod tester {
152    pub fn exit(status: i32) -> ! { panic!("expected exit with {}", status); }
153    pub fn stderr() -> DummyStderr { DummyStderr::new() }
154
155    use std::cell::RefCell;
156    use std::io;
157    use std::io::Result;
158    thread_local! {
159        static STDERR_BUF: RefCell<Vec<u8>> = RefCell::new(Vec::new());
160    }
161    pub struct DummyStderr();
162    impl DummyStderr {
163        pub fn new() -> DummyStderr { DummyStderr() }
164    }
165    impl io::Write for DummyStderr {
166        fn write(&mut self, buf: &[u8]) -> Result<usize> {
167            STDERR_BUF.with(|v| v.borrow_mut().extend_from_slice(buf));
168            Ok(buf.len())
169        }
170        fn flush(&mut self) -> Result<()> { Ok(()) }
171    }
172    pub fn get_stderr() -> Vec<u8> {
173        STDERR_BUF.with(|v| v.borrow().clone())
174    }
175}
176
177#[cfg(test)]
178mod tests {
179    use std::ffi::OsStr;
180    use super::*;
181
182    // The status 0 is a bit dangerous.  The test runner assumes the
183    // tests are successful if the exit status is 0, even when the
184    // dummy exit is not working and the process really exits before
185    // completing the tests,
186    #[test]
187    #[should_panic(expected = "expected exit with 0")]
188    fn err1() {
189        err!(0, "err 1");
190    }
191
192    #[test]
193    #[should_panic(expected = "expected exit with 9")]
194    fn err2() {
195        err!(9, "err {}", 2);
196    }
197
198    #[test]
199    #[should_panic(expected = "expected exit with 0")]
200    fn errp3() {
201        errp!(0, Path::new("Path"), "{} {}", "errp", 3);
202    }
203
204    #[test]
205    fn warn() {
206        warn!("warn 1");
207        assert!(tester::get_stderr().ends_with(b": warn 1\n"));
208        warn!("warn {}", 2);
209        assert!(tester::get_stderr().ends_with(b": warn 2\n"));
210        warn!("{} {}", "warn", 3);
211        assert!(tester::get_stderr().ends_with(b": warn 3\n"));
212    }
213
214    #[test]
215    fn warnp() {
216        warnp!("str", "warnp 1");
217        assert!(tester::get_stderr().ends_with(b": str: warnp 1\n"));
218        warnp!("str", "warnp {}", 2);
219        assert!(tester::get_stderr().ends_with(b": str: warnp 2\n"));
220        warnp!("str", "{} {}", "warnp", 3);
221        assert!(tester::get_stderr().ends_with(b": str: warnp 3\n"));
222        warnp!(Path::new("Path"), "warnp 1");
223        assert!(tester::get_stderr().ends_with(b": Path: warnp 1\n"));
224        warnp!(OsStr::new("OsStr"), "warnp 1");
225        assert!(tester::get_stderr().ends_with(b": OsStr: warnp 1\n"));
226    }
227
228    #[test]
229    #[should_panic(expected = "expected exit with 1")]
230    fn err_named_param() {
231        err!(1, "x = {x}, y = {y}", y = 20, x = 10);
232    }
233
234    #[test]
235    fn warn_named_param() {
236        warn!("x = {x}, y = {y}", y = 20, x = 10);
237        assert!(tester::get_stderr().ends_with(b": x = 10, y = 20\n"));
238    }
239}