cortex/frontend/
helpers.rs1use crate::frontend::params::TemplateContext;
4
5pub fn report_timestamp() -> String {
13 let now = chrono::Local::now();
14 let abbrev = local_tz_abbrev();
15 if abbrev.is_empty() {
16 now.format("%a, %d %b %Y %H:%M %Z").to_string()
17 } else {
18 format!("{} {}", now.format("%a, %d %b %Y %H:%M"), abbrev)
19 }
20}
21
22fn local_tz_abbrev() -> String {
26 unsafe {
30 let now: libc::time_t = libc::time(std::ptr::null_mut());
31 let mut tm: libc::tm = std::mem::zeroed();
32 if libc::localtime_r(&now, &mut tm).is_null() {
33 return String::new();
34 }
35 let mut buf = [0u8; 16];
36 let written = libc::strftime(
37 buf.as_mut_ptr() as *mut libc::c_char,
38 buf.len(),
39 c"%Z".as_ptr(),
40 &tm,
41 );
42 String::from_utf8_lossy(&buf[..written]).into_owned()
43 }
44}
45
46pub fn iso_utc(time: chrono::NaiveDateTime) -> String {
53 time
56 .and_utc()
57 .to_rfc3339_opts(chrono::SecondsFormat::Secs, true)
58}
59
60pub fn group_thousands(n: i64) -> String {
64 let digits = n.unsigned_abs().to_string();
65 let mut grouped = String::new();
66 for (i, ch) in digits.chars().enumerate() {
67 if i > 0 && (digits.len() - i).is_multiple_of(3) {
68 grouped.push(',');
69 }
70 grouped.push(ch);
71 }
72 if n < 0 {
73 format!("-{grouped}")
74 } else {
75 grouped
76 }
77}
78
79pub fn severity_highlight(severity: &str) -> &str {
81 match severity {
82 "no_problem" => "success",
84 "warning" => "warning",
85 "error" => "error",
86 "fatal" => "danger",
87 "invalid" => "info",
88 _ => "info",
89 }
90}
91pub fn uri_unescape(param: Option<&str>) -> Option<String> {
94 match param {
95 None => None,
96 Some(param_encoded) => {
97 let mut param_decoded: String = param_encoded.to_owned();
98 for &(original, replacement) in &[
100 ("%3A", ":"),
101 ("%2F", "/"),
102 ("%24", "$"),
103 ("%2E", "."),
104 ("%21", "!"),
105 ("%40", "@"),
106 ] {
107 param_decoded = param_decoded.replace(original, replacement);
108 }
109 Some(
110 percent_encoding::percent_decode(param_decoded.as_bytes())
111 .decode_utf8_lossy()
112 .into_owned(),
113 )
114 },
115 }
116}
117pub fn uri_escape(param: Option<String>) -> Option<String> {
120 match param {
121 None => None,
122 Some(param_pure) => {
123 let mut param_encoded: String =
124 percent_encoding::utf8_percent_encode(¶m_pure, percent_encoding::NON_ALPHANUMERIC)
125 .collect::<String>();
126 for &(original, replacement) in &[
128 (":", "%3A"),
129 ("/", "%2F"),
130 ("\\", "%5C"),
131 ("$", "%24"),
132 (".", "%2E"),
133 ("!", "%21"),
134 ("@", "%40"),
135 ] {
136 param_encoded = param_encoded.replace(original, replacement);
137 }
138 Some(param_encoded)
144 },
145 }
146}
147pub fn decorate_uri_encodings(context: &mut TemplateContext) {
150 for inner_vec in &mut [
151 &mut context.corpora,
152 &mut context.services,
153 &mut context.entries,
154 &mut context.categories,
155 &mut context.whats,
156 ] {
157 if let Some(ref mut inner_vec_data) = **inner_vec {
158 for subhash in inner_vec_data {
159 let mut uri_decorations = vec![];
160 for (subkey, subval) in subhash.iter() {
161 uri_decorations.push((
162 subkey.to_string() + "_uri",
163 uri_escape(Some(subval.to_string())).unwrap_or_default(),
164 ));
165 }
166 for (decoration_key, decoration_val) in uri_decorations {
167 subhash.insert(decoration_key, decoration_val);
168 }
169 }
170 }
171 }
172 let mut uri_decorations = vec![];
174 for (subkey, subval) in &context.global {
175 uri_decorations.push((
176 subkey.to_string() + "_uri",
177 uri_escape(Some(subval.to_string())).unwrap_or_default(),
178 ));
179 }
180 for (decoration_key, decoration_val) in uri_decorations {
181 context.global.insert(decoration_key, decoration_val);
182 }
183 let mut current_link = String::new();
184 {
185 if let Some(corpus_name) = context.global.get("corpus_name_uri")
186 && let Some(service_name) = context.global.get("service_name_uri")
187 {
188 current_link = format!("/corpus/{corpus_name}/{service_name}/");
189 if let Some(severity) = context.global.get("severity_uri") {
190 current_link.push_str(severity);
191 current_link.push('/');
192 if let Some(category) = context.global.get("category_uri") {
193 current_link.push_str(category);
194 current_link.push('/');
195 if let Some(what) = context.global.get("what_uri") {
196 current_link.push_str(what);
197 }
198 }
199 }
200 }
201 }
202 if !current_link.is_empty() {
203 context
204 .global
205 .insert("current_link_uri".to_string(), current_link);
206 }
207}
208
209#[cfg(test)]
210mod tests {
211 use super::group_thousands;
212
213 #[test]
214 fn group_thousands_inserts_separators() {
215 assert_eq!(group_thousands(0), "0");
216 assert_eq!(group_thousands(7), "7");
217 assert_eq!(group_thousands(999), "999");
218 assert_eq!(group_thousands(1000), "1,000");
219 assert_eq!(group_thousands(12345), "12,345");
220 assert_eq!(group_thousands(2_820_484), "2,820,484");
221 assert_eq!(group_thousands(-1234), "-1,234");
222 }
223}