evobench_tools/run/output_directory/
html_files.rs1use std::{
6 borrow::Cow,
7 collections::{BTreeMap, BTreeSet, btree_map::Entry},
8 io::Write,
9 sync::Arc,
10};
11
12use ahtml::{ASlice, HtmlAllocator, Node, SerHtmlFrag};
13use anyhow::Result;
14use kstring::KString;
15
16use crate::{
17 io_utils::tempfile_utils::tempfile,
18 output_table::{CellValue, OutputTable, OutputTableTitle, html::HtmlTable},
19 run::{
20 config::{RunConfig, ShareableConfig},
21 output_directory::structure::{ParametersDir, ToPath},
22 run_queues::RunQueues,
23 sub_command::list::{OutputTableOpts, ParameterView},
24 working_directory_pool::WorkingDirectoryPoolBaseDir,
25 },
26 utillib::{arc::CloneArc, into_arc_path::IntoArcPath, invert_index::invert_index_by_ref},
27};
28
29fn print_html_document(
30 body: ASlice<Node>,
31 html: &HtmlAllocator,
32 mut out: impl Write,
33) -> Result<()> {
34 let code: Arc<str> = Arc::from(
39 r##"
40<script>
41 if (location.hash) {
42 history.scrollRestoration = "manual";
43 }
44
45 window.addEventListener("DOMContentLoaded", () => {
46 if (location.hash.length > 0) {
47 const id = location.hash.substring(1);
48 const el = document.getElementById(id);
49 if (el) {
50 if (id.startsWith("end-")) {
51 el.scrollIntoView({ block: "end" });
52 } else {
53 el.scrollIntoView();
54 }
55 }
56 }
57 });
58</script>
59"##,
60 );
61
62 let doc = html.html(
63 [],
64 [
65 html.head(
66 [],
67 html.preserialized(SerHtmlFrag {
68 meta: &ahtml::SCRIPT_META,
69 string: code,
70 })?,
71 )?,
72 html.body([], html.table([], body)?)?,
73 ],
74 )?;
75 html.print_html_document(doc, &mut out)?;
76 out.flush()?;
77 Ok(())
78}
79
80pub fn print_list(
86 conf: &RunConfig,
87 working_directory_base_dir: &Arc<WorkingDirectoryPoolBaseDir>,
88 queues: &RunQueues,
89 output_table_opts: &OutputTableOpts,
90 html: Option<&HtmlAllocator>,
91 link_skipped: Option<&str>,
92 out: impl Write,
93) -> Result<()> {
94 let tmp;
95 let html = if let Some(html) = html {
96 html
97 } else {
98 tmp = HtmlAllocator::new(1000000, Arc::new("list"));
99 &tmp
100 };
101 let num_columns = output_table_opts
102 .parameter_view
103 .unwrap_or_default()
104 .titles()
105 .len();
106 let table = HtmlTable::new(num_columns, &html);
107 let body = output_table_opts.output_to_table(
108 table,
109 conf,
110 link_skipped,
111 working_directory_base_dir,
112 queues,
113 )?;
114 print_html_document(body.as_slice(), html, out)
115}
116
117fn write_2_column_table_file<'url, T1: CellValue<'url>, T2: CellValue<'url>>(
118 file_name: &str,
119 titles: &[&str],
120 index: &BTreeMap<T1, BTreeSet<T2>>,
121 conf: &RunConfig,
122 html: &HtmlAllocator,
123) -> Result<()> {
124 let titles: Vec<_> = titles
130 .iter()
131 .map(|title| OutputTableTitle {
132 text: (*title).into(),
133 span: 1,
134 anchor_name: None,
135 })
136 .collect();
137
138 let (tmp_file, out) = tempfile(conf.output_dir.path.join(file_name), false)?;
139 let num_columns = titles.len();
140 let mut table = HtmlTable::new(num_columns, &html);
141 table.write_title_row(&titles, None)?;
142
143 for (k, vs) in index {
144 let mut vs = vs.iter();
152 let v = vs
153 .next()
154 .expect("only adding BtreeSet with a value and never removing values");
155 let row: &[&dyn CellValue] = &[k, v];
156 table.write_data_row(row, None)?;
157
158 for v in vs {
159 let row: &[&dyn CellValue] = &[&"", v];
160 table.write_data_row(row, None)?;
161 }
162 }
163
164 print_html_document(table.finish()?.as_slice(), &html, out)?;
165 tmp_file.finish()?;
166 Ok(())
167}
168
169#[derive(PartialEq, Eq, PartialOrd, Ord)]
170struct ParametersCellValue {
171 dir: ParametersDir,
172 s: String,
173}
174
175impl From<ParametersDir> for ParametersCellValue {
176 fn from(dir: ParametersDir) -> Self {
177 let s = format!(
178 "{} -> {}",
179 dir.target_name().as_str(),
180 dir.custom_parameters()
181 );
182 Self { dir, s }
183 }
184}
185
186impl AsRef<str> for ParametersCellValue {
187 fn as_ref(&self) -> &str {
188 &self.s
189 }
190}
191
192impl<'url> CellValue<'url> for ParametersCellValue {
193 fn perhaps_url(&self) -> Option<Cow<'static, str>> {
194 Some(self.dir.to_path().to_string_lossy().to_string().into())
195 }
196 fn perhaps_anchor_name(&self) -> Option<&KString> {
197 None
198 }
199}
200
201impl<'url> CellValue<'url> for &ParametersCellValue {
202 fn perhaps_url(&self) -> Option<Cow<'static, str>> {
203 Some(self.dir.to_path().to_string_lossy().to_string().into())
204 }
205 fn perhaps_anchor_name(&self) -> Option<&KString> {
206 None
207 }
208}
209
210impl<'url> CellValue<'url> for KString {
211 fn perhaps_url(&self) -> Option<Cow<'static, str>> {
212 None
213 }
214 fn perhaps_anchor_name(&self) -> Option<&KString> {
215 None
216 }
217}
218
219impl<'url> CellValue<'url> for &KString {
220 fn perhaps_url(&self) -> Option<Cow<'static, str>> {
221 None
222 }
223 fn perhaps_anchor_name(&self) -> Option<&KString> {
224 None
225 }
226}
227
228pub fn regenerate_index_files(
232 shareable_config: &ShareableConfig,
233 working_directory_base_dir: Option<&Arc<WorkingDirectoryPoolBaseDir>>,
234 queues: Option<&RunQueues>,
235) -> Result<()> {
236 let conf = &shareable_config.run_config;
237
238 let tmp;
241 let working_directory_base_dir = if let Some(d) = working_directory_base_dir {
242 d
243 } else {
244 tmp = Arc::new(WorkingDirectoryPoolBaseDir::new(
245 conf.working_directory_pool.base_dir.clone(),
246 &|| {
247 shareable_config
248 .global_app_state_dir
249 .working_directory_pool_base()
250 },
251 )?);
252 &tmp
253 };
254
255 let tmp2;
256 let queues = if let Some(q) = queues {
257 q
258 } else {
259 tmp2 = RunQueues::open(
260 shareable_config.run_config.queues.clone_arc(),
261 true,
262 &shareable_config.global_app_state_dir,
263 None,
265 )?;
266 &tmp2
267 };
268
269 let mut html = HtmlAllocator::new(1000000, Arc::new("regenerate_index_files"));
272
273 let write_jobs_list =
274 |html: &HtmlAllocator, file_name: &str, all: bool, link: Option<&str>| -> Result<()> {
275 let output_table_opts = OutputTableOpts {
276 verbose: false,
277 all,
278 n: None,
279 parameter_view: Some(ParameterView::Separated),
280 };
281
282 let (tmp_file, out) = tempfile(conf.output_dir.path.join(file_name), false)?;
283
284 print_list(
285 conf,
286 working_directory_base_dir,
287 queues,
288 &output_table_opts,
289 Some(html),
290 link,
291 out,
292 )?;
293
294 tmp_file.finish()?;
295
296 Ok(())
297 };
298
299 write_jobs_list(&html, "list.html", false, Some("list-unlimited.html"))?;
301 html.clear();
302 write_jobs_list(&html, "list-unlimited.html", true, None)?;
306 html.clear();
307
308 if let Some(base_url) = &conf.output_dir.url {
310 let paths_with_names = {
311 let mut paths_with_names = BTreeMap::new();
312 for (name, templates) in &conf.job_template_lists {
313 for template in &**templates {
314 let dir = ParametersCellValue::from(
315 template.to_parameters_dir(base_url.into_arc_path()),
316 );
317 match paths_with_names.entry(dir) {
318 Entry::Vacant(vacant_entry) => {
319 let mut m = BTreeSet::new();
320 m.insert(name.clone());
321 vacant_entry.insert(m);
322 }
323 Entry::Occupied(mut occupied_entry) => {
324 occupied_entry.get_mut().insert(name.clone());
325 }
326 }
327 }
328 }
329 paths_with_names
330 };
331
332 write_2_column_table_file(
333 "by_parameters.html",
334 &["Parameter", "Templates names"],
335 &paths_with_names,
336 conf,
337 &html,
338 )?;
339 html.clear();
340
341 let names_with_paths = invert_index_by_ref(&paths_with_names);
342 write_2_column_table_file(
343 "by_templates_name.html",
344 &["Templates name", "Parameters"],
345 &names_with_paths,
346 conf,
347 &html,
348 )?;
349 html.clear();
350 }
351
352 Ok(())
353}