walker_common/report/
mod.rs1mod stats;
4mod summary;
5
6pub use stats::*;
7pub use summary::*;
8
9use std::fmt::Display;
10use std::io::Write;
11use time::macros::format_description;
12
13const BOOTSTRAP_VERSION: &str = "5.3.3";
14const BOOTSTRAP_CSS_SRI: &str =
15 "sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH";
16const BOOTSTRAP_JS_SRI: &str =
17 "sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz";
18
19#[derive(Clone, Debug, Default)]
21pub struct ReportOptions {
22 pub bootstrap: Bootstrap,
23}
24
25#[derive(Clone, Default, Debug)]
27pub enum Bootstrap {
28 #[default]
30 Default,
31 Custom {
33 location: String,
37 js_location: Option<String>,
39 css_integrity: Option<String>,
41 js_integrity: Option<String>,
43 },
44}
45
46impl Bootstrap {
47 pub fn css_location(&self) -> String {
48 match self {
49 Self::Default => format!("https://cdn.jsdelivr.net/npm/bootstrap@{BOOTSTRAP_VERSION}/dist/css/bootstrap.min.css"),
50 Self::Custom {location, js_location, ..} => match js_location {
51 Some(_) => location.clone(),
52 None => format!("{location}/css/bootstrap.min.css" ),
53 },
54 }
55 }
56
57 pub fn css_integrity(&self) -> Option<String> {
58 match self {
59 Self::Default => Some(BOOTSTRAP_CSS_SRI.into()),
60 Self::Custom { css_integrity, .. } => css_integrity.clone(),
61 }
62 }
63
64 pub fn js_location(&self) -> String {
65 match self {
66 Self::Default => format!("https://cdn.jsdelivr.net/npm/bootstrap@{BOOTSTRAP_VERSION}/dist/js/bootstrap.bundle.min.js"),
67 Self::Custom {location, js_location, ..} => {
68 match js_location {
69 Some(js_location) => js_location.clone(),
70 None => format!("{location}/js/bootstrap.bundle.min.js")
71 }
72 }
73 }
74 }
75
76 pub fn js_integrity(&self) -> Option<String> {
77 match self {
78 Self::Default => Some(BOOTSTRAP_JS_SRI.into()),
79 Self::Custom { js_integrity, .. } => js_integrity.clone(),
80 }
81 }
82}
83
84pub fn render(
85 mut write: impl Write,
86 title: impl Display,
87 report: impl Display,
88 options: &ReportOptions,
89) -> anyhow::Result<()> {
90 writeln!(
91 write,
92 r#"<!doctype html>
93<html lang="en">
94 <head>
95 <meta charset="utf-8">
96 <meta name="viewport" content="width=device-width, initial-scale=1">
97 <title>{title}</title>
98 <link href="{css}" rel="stylesheet" {css_integrity} crossorigin="anonymous">
99 </head>
100 <body>
101 <div class="container-fluid">
102 <h1>
103 {title} <span class="badge bg-secondary">{date}</span>
104 </h1>
105 {report}
106 </div>
107 <script src="{js}" {js_integrity} crossorigin="anonymous"></script>
108 </body>
109</html>
110"#,
111 date = time::OffsetDateTime::now_local()
112 .unwrap_or_else(|_| time::OffsetDateTime::now_utc())
113 .format(&format_description!(
114 "[year]-[month padding:zero]-[day padding:zero] [hour repr:24]:[minute padding:zero]:[second padding:zero] [offset_hour sign:mandatory]:[offset_minute]"
115 ))
116 .unwrap_or_else(|_| "Unknown".to_string()),
117 css = html_escape::encode_quoted_attribute(&options.bootstrap.css_location()),
118 js = html_escape::encode_quoted_attribute(&options.bootstrap.js_location()),
119 css_integrity = options
120 .bootstrap
121 .css_integrity()
122 .map(|sri| format!(
123 r#"integrity="{sri}""#,
124 sri = html_escape::encode_quoted_attribute(&sri)
125 ))
126 .unwrap_or_default(),
127 js_integrity = options
128 .bootstrap
129 .js_integrity()
130 .map(|sri| format!(
131 r#"integrity="{sri}""#,
132 sri = html_escape::encode_quoted_attribute(&sri)
133 ))
134 .unwrap_or_default(),
135 title = html_escape::encode_text(&title.to_string()),
136 )?;
137
138 Ok(())
139}