Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
247 changes: 8 additions & 239 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,75 +48,15 @@
#[cfg(doctest)]
pub struct ReadmeDoctests;

pub mod report;
use report::{Method, Report};

use std::borrow::Cow;
use std::io::Result as IoResult;
use std::panic::PanicHookInfo;
use std::path::{Path, PathBuf};

/// A convenient metadata struct that describes a crate
///
/// See [`metadata!`]
pub struct Metadata {
name: Cow<'static, str>,
version: Cow<'static, str>,
authors: Option<Cow<'static, str>>,
homepage: Option<Cow<'static, str>>,
repository: Option<Cow<'static, str>>,
support: Option<Cow<'static, str>>,
}

impl Metadata {
/// See [`metadata!`]
pub fn new(name: impl Into<Cow<'static, str>>, version: impl Into<Cow<'static, str>>) -> Self {
Self {
name: name.into(),
version: version.into(),
authors: None,
homepage: None,
repository: None,
support: None,
}
}
mod metadata;
mod panic;

/// The list of authors of the crate
pub fn authors(mut self, value: impl Into<Cow<'static, str>>) -> Self {
let value = value.into();
if !value.is_empty() {
self.authors = value.into();
}
self
}

/// The URL of the crate's website
pub fn homepage(mut self, value: impl Into<Cow<'static, str>>) -> Self {
let value = value.into();
if !value.is_empty() {
self.homepage = value.into();
}
self
}

/// The URL of the crate's repository
pub fn repository(mut self, value: impl Into<Cow<'static, str>>) -> Self {
let value = value.into();
if !value.is_empty() {
self.repository = value.into();
}
self
}

/// The support information
pub fn support(mut self, value: impl Into<Cow<'static, str>>) -> Self {
let value = value.into();
if !value.is_empty() {
self.support = value.into();
}
self
}
}
pub mod report;
pub use metadata::Metadata;
pub use panic::PanicStyle;
pub use panic::handle_dump;
pub use panic::print_msg;
pub use panic::setup_panic;

/// Initialize [`Metadata`]
#[macro_export]
Expand Down Expand Up @@ -161,174 +101,3 @@ macro_rules! setup_panic {
$crate::setup_panic!($crate::metadata!());
};
}

#[doc(hidden)]
pub fn setup_panic(meta: impl Fn() -> Metadata) {
#![allow(deprecated)]

#[allow(unused_imports)]
use std::panic;

match PanicStyle::default() {
PanicStyle::Debug => {}
PanicStyle::Human => {
let meta = meta();

panic::set_hook(Box::new(move |info: &PanicHookInfo<'_>| {
let file_path = handle_dump(&meta, info);
print_msg(file_path, &meta)
.expect("human-panic: printing error message to console failed");
}));
}
}
}

/// Style of panic to be used
#[non_exhaustive]
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum PanicStyle {
/// Normal panic
Debug,
/// Human-formatted panic
Human,
}

impl Default for PanicStyle {
fn default() -> Self {
if cfg!(debug_assertions) {
PanicStyle::Debug
} else {
match ::std::env::var("RUST_BACKTRACE") {
Ok(_) => PanicStyle::Debug,
Err(_) => PanicStyle::Human,
}
}
}
}

/// Utility function that prints a message to our human users
#[cfg(feature = "color")]
pub fn print_msg<P: AsRef<Path>>(file_path: Option<P>, meta: &Metadata) -> IoResult<()> {
use std::io::Write as _;

let stderr = anstream::stderr();
let mut stderr = stderr.lock();

write!(stderr, "{}", anstyle::AnsiColor::Red.render_fg())?;
write_msg(&mut stderr, file_path, meta)?;
write!(stderr, "{}", anstyle::Reset.render())?;

Ok(())
}

#[cfg(not(feature = "color"))]
pub fn print_msg<P: AsRef<Path>>(file_path: Option<P>, meta: &Metadata) -> IoResult<()> {
let stderr = std::io::stderr();
let mut stderr = stderr.lock();

write_msg(&mut stderr, file_path, meta)?;

Ok(())
}

fn write_msg<P: AsRef<Path>>(
buffer: &mut impl std::io::Write,
file_path: Option<P>,
meta: &Metadata,
) -> IoResult<()> {
let Metadata {
name,
authors,
homepage,
repository,
support,
..
} = meta;

writeln!(buffer, "Well, this is embarrassing.\n")?;
writeln!(
buffer,
"{name} had a problem and crashed. To help us diagnose the \
problem you can send us a crash report.\n"
)?;
writeln!(
buffer,
"We have generated a report file at \"{}\". Submit an \
issue or email with the subject of \"{} Crash Report\" and include the \
report as an attachment.\n",
match file_path {
Some(fp) => format!("{}", fp.as_ref().display()),
None => "<Failed to store file to disk>".to_owned(),
},
name
)?;

if let Some(homepage) = homepage {
writeln!(buffer, "- Homepage: {homepage}")?;
} else if let Some(repository) = repository {
writeln!(buffer, "- Repository: {repository}")?;
}
if let Some(authors) = authors {
writeln!(buffer, "- Authors: {authors}")?;
}
if let Some(support) = support {
writeln!(buffer, "\nTo submit the crash report:\n\n{support}")?;
}
writeln!(
buffer,
"\nWe take privacy seriously, and do not perform any \
automated error collection. In order to improve the software, we rely on \
people to submit reports.\n"
)?;
writeln!(buffer, "Thank you kindly!")?;

Ok(())
}

/// Utility function which will handle dumping information to disk
#[allow(deprecated)]
pub fn handle_dump(meta: &Metadata, panic_info: &PanicHookInfo<'_>) -> Option<PathBuf> {
let mut expl = String::new();

let message = match (
panic_info.payload().downcast_ref::<&str>(),
panic_info.payload().downcast_ref::<String>(),
) {
(Some(s), _) => Some((*s).to_owned()),
(_, Some(s)) => Some(s.to_owned()),
(None, None) => None,
};

let cause = match message {
Some(m) => m,
None => "Unknown".into(),
};

match panic_info.location() {
Some(location) => expl.push_str(&format!(
"Panic occurred in file '{}' at line {}\n",
location.file(),
location.line()
)),
None => expl.push_str("Panic location unknown.\n"),
}

let report = Report::new(&meta.name, &meta.version, Method::Panic, expl, cause);

if let Ok(f) = report.persist() {
Some(f)
} else {
use std::io::Write as _;
let stderr = std::io::stderr();
let mut stderr = stderr.lock();

let _ = writeln!(
stderr,
"{}",
report
.serialize()
.expect("only doing toml compatible types")
);
None
}
}
63 changes: 63 additions & 0 deletions src/metadata.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use std::borrow::Cow;

/// A convenient metadata struct that describes a crate
///
/// See [`metadata!`][crate::metadata!]
pub struct Metadata {
pub(crate) name: Cow<'static, str>,
pub(crate) version: Cow<'static, str>,
pub(crate) authors: Option<Cow<'static, str>>,
pub(crate) homepage: Option<Cow<'static, str>>,
pub(crate) repository: Option<Cow<'static, str>>,
pub(crate) support: Option<Cow<'static, str>>,
}

impl Metadata {
/// See [`metadata!`][crate::metadata!]
pub fn new(name: impl Into<Cow<'static, str>>, version: impl Into<Cow<'static, str>>) -> Self {
Self {
name: name.into(),
version: version.into(),
authors: None,
homepage: None,
repository: None,
support: None,
}
}

/// The list of authors of the crate
pub fn authors(mut self, value: impl Into<Cow<'static, str>>) -> Self {
let value = value.into();
if !value.is_empty() {
self.authors = value.into();
}
self
}

/// The URL of the crate's website
pub fn homepage(mut self, value: impl Into<Cow<'static, str>>) -> Self {
let value = value.into();
if !value.is_empty() {
self.homepage = value.into();
}
self
}

/// The URL of the crate's repository
pub fn repository(mut self, value: impl Into<Cow<'static, str>>) -> Self {
let value = value.into();
if !value.is_empty() {
self.repository = value.into();
}
self
}

/// The support information
pub fn support(mut self, value: impl Into<Cow<'static, str>>) -> Self {
let value = value.into();
if !value.is_empty() {
self.support = value.into();
}
self
}
}
Loading
Loading