1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
//! `bugreport` is a library that helps application developers to automatically collect
//! information about the system and the environment that users can send along with a bug
//! report (similar to `git bugreport` or `ffmpeg … -report`).
//!
//! Usage example:
//! ```
//! use bugreport::{bugreport, collector::*, format::Markdown};
//!
//! bugreport!()
//!     .info(SoftwareVersion::default())
//!     .info(OperatingSystem::default())
//!     .info(CommandLine::default())
//!     .info(EnvironmentVariables::list(&["SHELL", "EDITOR"]))
//!     .info(CommandOutput::new("Python version", "python", &["--version"]))
//!     .info(CompileTimeInformation::default())
//!     .print::<Markdown>();
//! ```

use std::result;

pub mod collector;
pub mod format;
mod helper;
pub mod report;

use collector::{CollectionError, Collector};
use format::Format;
use report::{Report, ReportSection};

pub(crate) type Result<T> = result::Result<T, CollectionError>;

#[doc(hidden)]
pub struct CrateInfo<'a> {
    pkg_name: &'a str,
    pkg_version: &'a str,
}

/// The main struct for collecting bug report information.
///
/// Use the [`bugreport`] macro to create one.
pub struct BugReport<'a> {
    info: CrateInfo<'a>,
    collectors: Vec<Box<dyn Collector>>,
}

impl<'a> BugReport<'a> {
    #[doc(hidden)]
    pub fn from_name_and_version(pkg_name: &'a str, pkg_version: &'a str) -> Self {
        BugReport {
            info: CrateInfo {
                pkg_name,
                pkg_version,
            },
            collectors: vec![],
        }
    }

    /// Add a [`Collector`] to the bug report.
    pub fn info<C: Collector + 'static>(mut self, collector: C) -> Self {
        self.collectors.push(Box::new(collector));
        self
    }

    fn generate(&mut self) -> Report {
        let mut sections = vec![];

        for collector in &mut self.collectors {
            let entry = collector
                .collect(&self.info)
                .unwrap_or_else(|e| e.to_entry());
            sections.push(ReportSection {
                title: collector.description(),
                entry,
            });
        }

        Report { sections }
    }

    /// Assemble the bug report information using the given format.
    pub fn format<F: Format>(&mut self) -> String {
        let mut format = F::default();
        self.generate().format_as(&mut format)
    }

    /// Print the bug report information using the given format.
    pub fn print<F: Format>(&mut self) {
        println!("{}", self.format::<F>());
    }
}

/// Generate a new [`BugReport`] object.
#[macro_export]
macro_rules! bugreport {
    () => {
        bugreport::BugReport::from_name_and_version(
            env!("CARGO_PKG_NAME"),
            env!("CARGO_PKG_VERSION"),
        )
    };
}

#[cfg(test)]
mod tests {
    #[test]
    #[cfg(feature = "format_markdown")]
    fn basic() {
        use super::BugReport;
        use crate::collector::*;
        use crate::format::Markdown;

        std::env::set_var("BUGREPORT_TEST", "42");

        let report = BugReport::from_name_and_version("dummy", "0.1")
            .info(EnvironmentVariables::list(&["BUGREPORT_TEST"]))
            .format::<Markdown>();

        assert_eq!(
            report,
            "#### Environment variables\n\
             \n\
             ```bash\n\
             BUGREPORT_TEST=42\n\
             ```\n\n"
        );
    }
}