bugreport/
lib.rs

1//! `bugreport` is a library that helps application developers to automatically collect
2//! information about the system and the environment that users can send along with a bug
3//! report (similar to `git bugreport` or `ffmpeg … -report`).
4//!
5//! Usage example:
6//! ```
7//! use bugreport::{bugreport, collector::*, format::Markdown};
8//!
9//! bugreport!()
10//!     .info(SoftwareVersion::default())
11//!     .info(OperatingSystem::default())
12//!     .info(CommandLine::default())
13//!     .info(EnvironmentVariables::list(&["SHELL", "EDITOR"]))
14//!     .info(CommandOutput::new("Python version", "python", &["--version"]))
15//!     .info(CompileTimeInformation::default())
16//!     .print::<Markdown>();
17//! ```
18
19use std::result;
20
21pub mod collector;
22pub mod format;
23mod helper;
24pub mod report;
25
26use collector::{CollectionError, Collector};
27use format::Format;
28use report::{Report, ReportSection};
29
30pub(crate) type Result<T> = result::Result<T, CollectionError>;
31
32#[doc(hidden)]
33pub struct CrateInfo<'a> {
34    pkg_name: &'a str,
35    pkg_version: &'a str,
36    git_hash: Option<&'a str>,
37}
38
39/// The main struct for collecting bug report information.
40///
41/// Use the [`bugreport`] macro to create one.
42pub struct BugReport<'a> {
43    info: CrateInfo<'a>,
44    collectors: Vec<Box<dyn Collector>>,
45}
46
47impl<'a> BugReport<'a> {
48    #[doc(hidden)]
49    pub fn from_name_and_version(pkg_name: &'a str, pkg_version: &'a str) -> Self {
50        BugReport {
51            info: CrateInfo {
52                pkg_name,
53                pkg_version,
54                git_hash: None,
55            },
56            collectors: vec![],
57        }
58    }
59
60    #[doc(hidden)]
61    pub fn set_git_hash(&mut self, git_hash: Option<&'a str>) {
62        self.info.git_hash = git_hash;
63    }
64
65    /// Add a [`Collector`] to the bug report.
66    pub fn info<C: Collector + 'static>(mut self, collector: C) -> Self {
67        self.collectors.push(Box::new(collector));
68        self
69    }
70
71    fn generate(&mut self) -> Report {
72        let mut sections = vec![];
73
74        for collector in &mut self.collectors {
75            let entry = collector
76                .collect(&self.info)
77                .unwrap_or_else(|e| e.to_entry());
78            sections.push(ReportSection {
79                title: collector.description(),
80                entry,
81            });
82        }
83
84        Report { sections }
85    }
86
87    /// Assemble the bug report information using the given format.
88    pub fn format<F: Format>(&mut self) -> String {
89        let mut format = F::default();
90        self.generate().format_as(&mut format)
91    }
92
93    /// Print the bug report information using the given format.
94    pub fn print<F: Format>(&mut self) {
95        println!("{}", self.format::<F>());
96    }
97}
98
99/// Re-export so dependent project does not have to manually depend on git-version crate
100#[cfg(feature = "git_hash")]
101pub use git_version::git_version;
102
103#[cfg(feature = "git_hash")]
104#[doc(hidden)]
105#[macro_export]
106macro_rules! bugreport_set_git_hash {
107    ($br:ident) => {{
108        let hash = bugreport::git_version!(fallback = "");
109        if !hash.is_empty() {
110            $br.set_git_hash(Some(hash));
111        }
112    }};
113}
114
115#[cfg(not(feature = "git_hash"))]
116#[doc(hidden)]
117#[macro_export]
118macro_rules! bugreport_set_git_hash {
119    ($br:ident) => {};
120}
121
122/// Generate a new [`BugReport`] object.
123#[macro_export]
124macro_rules! bugreport {
125    () => {{
126        let mut br = bugreport::BugReport::from_name_and_version(
127            env!("CARGO_PKG_NAME"),
128            env!("CARGO_PKG_VERSION"),
129        );
130        bugreport::bugreport_set_git_hash!(br);
131        br
132    }};
133}
134
135#[cfg(test)]
136mod tests {
137    #[test]
138    #[cfg(feature = "format_markdown")]
139    fn basic() {
140        use super::BugReport;
141        use crate::collector::*;
142        use crate::format::Markdown;
143
144        std::env::set_var("BUGREPORT_TEST", "42");
145
146        let report = BugReport::from_name_and_version("dummy", "0.1")
147            .info(EnvironmentVariables::list(&["BUGREPORT_TEST"]))
148            .format::<Markdown>();
149
150        assert_eq!(
151            report,
152            "#### Environment variables\n\
153             \n\
154             ```bash\n\
155             BUGREPORT_TEST=42\n\
156             ```\n\n"
157        );
158    }
159}