1#![cfg_attr(docsrs, feature(doc_cfg))]
44#![warn(clippy::print_stderr)]
45#![warn(clippy::print_stdout)]
46
47#[doc = include_str!("../README.md")]
48#[cfg(doctest)]
49pub struct ReadmeDoctests;
50
51pub mod report;
52use report::{Method, Report};
53
54use std::borrow::Cow;
55use std::io::Result as IoResult;
56use std::panic::PanicHookInfo;
57use std::path::{Path, PathBuf};
58
59pub struct Metadata {
63 name: Cow<'static, str>,
64 version: Cow<'static, str>,
65 authors: Option<Cow<'static, str>>,
66 homepage: Option<Cow<'static, str>>,
67 repository: Option<Cow<'static, str>>,
68 support: Option<Cow<'static, str>>,
69}
70
71impl Metadata {
72 pub fn new(name: impl Into<Cow<'static, str>>, version: impl Into<Cow<'static, str>>) -> Self {
74 Self {
75 name: name.into(),
76 version: version.into(),
77 authors: None,
78 homepage: None,
79 repository: None,
80 support: None,
81 }
82 }
83
84 pub fn authors(mut self, value: impl Into<Cow<'static, str>>) -> Self {
86 let value = value.into();
87 if !value.is_empty() {
88 self.authors = value.into();
89 }
90 self
91 }
92
93 pub fn homepage(mut self, value: impl Into<Cow<'static, str>>) -> Self {
95 let value = value.into();
96 if !value.is_empty() {
97 self.homepage = value.into();
98 }
99 self
100 }
101
102 pub fn repository(mut self, value: impl Into<Cow<'static, str>>) -> Self {
104 let value = value.into();
105 if !value.is_empty() {
106 self.repository = value.into();
107 }
108 self
109 }
110
111 pub fn support(mut self, value: impl Into<Cow<'static, str>>) -> Self {
113 let value = value.into();
114 if !value.is_empty() {
115 self.support = value.into();
116 }
117 self
118 }
119}
120
121#[macro_export]
123macro_rules! metadata {
124 () => {{
125 $crate::Metadata::new(env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"))
126 .authors(env!("CARGO_PKG_AUTHORS").replace(":", ", "))
127 .homepage(env!("CARGO_PKG_HOMEPAGE"))
128 .repository(env!("CARGO_PKG_REPOSITORY"))
129 }};
130}
131
132#[macro_export]
155macro_rules! setup_panic {
156 ($meta:expr) => {{
157 $crate::setup_panic(|| $meta);
158 }};
159
160 () => {
161 $crate::setup_panic!($crate::metadata!());
162 };
163}
164
165#[doc(hidden)]
166pub fn setup_panic(meta: impl Fn() -> Metadata) {
167 #![allow(deprecated)]
168
169 #[allow(unused_imports)]
170 use std::panic;
171
172 match PanicStyle::default() {
173 PanicStyle::Debug => {}
174 PanicStyle::Human => {
175 let meta = meta();
176
177 panic::set_hook(Box::new(move |info: &PanicHookInfo<'_>| {
178 let file_path = handle_dump(&meta, info);
179 print_msg(file_path, &meta)
180 .expect("human-panic: printing error message to console failed");
181 }));
182 }
183 }
184}
185
186#[non_exhaustive]
188#[derive(Copy, Clone, PartialEq, Eq)]
189pub enum PanicStyle {
190 Debug,
192 Human,
194}
195
196impl Default for PanicStyle {
197 fn default() -> Self {
198 if cfg!(debug_assertions) {
199 PanicStyle::Debug
200 } else {
201 match ::std::env::var("RUST_BACKTRACE") {
202 Ok(_) => PanicStyle::Debug,
203 Err(_) => PanicStyle::Human,
204 }
205 }
206 }
207}
208
209#[cfg(feature = "color")]
211pub fn print_msg<P: AsRef<Path>>(file_path: Option<P>, meta: &Metadata) -> IoResult<()> {
212 use std::io::Write as _;
213
214 let stderr = anstream::stderr();
215 let mut stderr = stderr.lock();
216
217 write!(stderr, "{}", anstyle::AnsiColor::Red.render_fg())?;
218 write_msg(&mut stderr, file_path, meta)?;
219 write!(stderr, "{}", anstyle::Reset.render())?;
220
221 Ok(())
222}
223
224#[cfg(not(feature = "color"))]
225pub fn print_msg<P: AsRef<Path>>(file_path: Option<P>, meta: &Metadata) -> IoResult<()> {
226 let stderr = std::io::stderr();
227 let mut stderr = stderr.lock();
228
229 write_msg(&mut stderr, file_path, meta)?;
230
231 Ok(())
232}
233
234fn write_msg<P: AsRef<Path>>(
235 buffer: &mut impl std::io::Write,
236 file_path: Option<P>,
237 meta: &Metadata,
238) -> IoResult<()> {
239 let Metadata {
240 name,
241 authors,
242 homepage,
243 repository,
244 support,
245 ..
246 } = meta;
247
248 writeln!(buffer, "Well, this is embarrassing.\n")?;
249 writeln!(
250 buffer,
251 "{name} had a problem and crashed. To help us diagnose the \
252 problem you can send us a crash report.\n"
253 )?;
254 writeln!(
255 buffer,
256 "We have generated a report file at \"{}\". Submit an \
257 issue or email with the subject of \"{} Crash Report\" and include the \
258 report as an attachment.\n",
259 match file_path {
260 Some(fp) => format!("{}", fp.as_ref().display()),
261 None => "<Failed to store file to disk>".to_owned(),
262 },
263 name
264 )?;
265
266 if let Some(homepage) = homepage {
267 writeln!(buffer, "- Homepage: {homepage}")?;
268 } else if let Some(repository) = repository {
269 writeln!(buffer, "- Repository: {repository}")?;
270 }
271 if let Some(authors) = authors {
272 writeln!(buffer, "- Authors: {authors}")?;
273 }
274 if let Some(support) = support {
275 writeln!(buffer, "\nTo submit the crash report:\n\n{support}")?;
276 }
277 writeln!(
278 buffer,
279 "\nWe take privacy seriously, and do not perform any \
280 automated error collection. In order to improve the software, we rely on \
281 people to submit reports.\n"
282 )?;
283 writeln!(buffer, "Thank you kindly!")?;
284
285 Ok(())
286}
287
288#[allow(deprecated)]
290pub fn handle_dump(meta: &Metadata, panic_info: &PanicHookInfo<'_>) -> Option<PathBuf> {
291 let mut expl = String::new();
292
293 let message = match (
294 panic_info.payload().downcast_ref::<&str>(),
295 panic_info.payload().downcast_ref::<String>(),
296 ) {
297 (Some(s), _) => Some((*s).to_owned()),
298 (_, Some(s)) => Some(s.to_owned()),
299 (None, None) => None,
300 };
301
302 let cause = match message {
303 Some(m) => m,
304 None => "Unknown".into(),
305 };
306
307 match panic_info.location() {
308 Some(location) => expl.push_str(&format!(
309 "Panic occurred in file '{}' at line {}\n",
310 location.file(),
311 location.line()
312 )),
313 None => expl.push_str("Panic location unknown.\n"),
314 }
315
316 let report = Report::new(&meta.name, &meta.version, Method::Panic, expl, cause);
317
318 if let Ok(f) = report.persist() {
319 Some(f)
320 } else {
321 use std::io::Write as _;
322 let stderr = std::io::stderr();
323 let mut stderr = stderr.lock();
324
325 let _ = writeln!(
326 stderr,
327 "{}",
328 report
329 .serialize()
330 .expect("only doing toml compatible types")
331 );
332 None
333 }
334}