From 2fa62ab846ae1116757e3a3a77ae4c0ca89bcaa1 Mon Sep 17 00:00:00 2001 From: Lars Wirzenius Date: Sun, 31 Mar 2024 12:27:15 +0300 Subject: [PATCH] feat: add a table of contents to the run log Signed-off-by: Lars Wirzenius --- src/bin/run_log.rs | 7 ++++ src/runlog.rs | 84 +++++++++++++++++++++++++++++++++------------- 2 files changed, 67 insertions(+), 24 deletions(-) diff --git a/src/bin/run_log.rs b/src/bin/run_log.rs index a6f3d19..c327b77 100644 --- a/src/bin/run_log.rs +++ b/src/bin/run_log.rs @@ -17,6 +17,13 @@ fn main() { "This is stdout".as_bytes(), "Error messages go here".as_bytes(), ); + run_log.runcmd( + &["cargo", "check", "--all-targets", "--workspace"], + Path::new("/tmp"), + 0, + "This is stdout".as_bytes(), + "Error messages go here".as_bytes(), + ); run_log.write().expect("write html log"); } diff --git a/src/runlog.rs b/src/runlog.rs index d23a098..13b9b99 100644 --- a/src/runlog.rs +++ b/src/runlog.rs @@ -67,7 +67,6 @@ impl RunLog { let mut doc = Document::default(); doc.push_to_head(&Element::new(Tag::Title).with_text(title)); - doc.push_to_body(&Element::new(Tag::H1).with_text(title)); let mut ul = Element::new(Tag::Ul); @@ -95,15 +94,20 @@ impl RunLog { doc.push_to_body(&error); } - for cmd in self.commands.iter() { - cmd.format(&mut doc); - } - if self.timeout { let timeout = Element::new(Tag::P).with_text("Last command timed out"); doc.push_to_body(&timeout); } + let mut toc = ToC::default(); + let mut body = Element::new(Tag::Div); + for cmd in self.commands.iter() { + cmd.format(&mut body, &mut toc); + } + + doc.push_to_body(&toc.as_html()); + doc.push_to_body(&body); + let html = format!("{}", doc); std::fs::write(&self.filename, html.as_bytes()) .map_err(|e| RunLogError::Write(self.filename.clone(), e))?; @@ -134,14 +138,16 @@ impl Command { self.argv.as_slice().join(" ") } - fn format(&self, doc: &mut Document) { - doc.push_to_body( - &Element::new(Tag::H2) - .with_text("Run: ") - .with_child(Element::new(Tag::Code).with_text(&self.command())), + fn format(&self, body: &mut Element, toc: &mut ToC) { + body.push_child( + &toc.push( + Element::new(Tag::Span) + .with_text("Run: ") + .with_child(Element::new(Tag::Code).with_text(&self.command())), + ), ); - doc.push_to_body(&Element::new(Tag::P).with_text("Command arguments:")); + body.push_child(&Element::new(Tag::P).with_text("Command arguments:")); let mut ul = Element::new(Tag::Ul); for arg in self.argv.iter() { ul.push_child( @@ -149,34 +155,64 @@ impl Command { .with_child(Element::new(Tag::Code).with_text(&format!("{:?}", arg))), ); } - doc.push_to_body(&ul); + body.push_child(&ul); - doc.push_to_body( + body.push_child( &Element::new(Tag::P) .with_text("In directory: ") .with_child(Element::new(Tag::Code).with_text(&format!("{}", self.cwd.display()))), ); - doc.push_to_body(&Element::new(Tag::P).with_text(&format!("Exit code: {}", self.exit))); + body.push_child(&Element::new(Tag::P).with_text(&format!("Exit code: {}", self.exit))); - self.output(doc, "Stdout:", &self.stdout); - self.output(doc, "Stderr:", &self.stderr); + self.output(body, "Stdout:", &self.stdout); + self.output(body, "Stderr:", &self.stderr); } - fn output(&self, doc: &mut Document, stream: &str, data: &[u8]) { + fn output(&self, body: &mut Element, stream: &str, data: &[u8]) { if !data.is_empty() { - doc.push_to_body(&Element::new(Tag::P).with_text(stream)); - doc.push_to_body( - &Element::new(Tag::P).with_child( - Element::new(Tag::Blockquote).with_child( - Element::new(Tag::Pre).with_text(&String::from_utf8_lossy(data)), - ), - ), + body.push_child(&Element::new(Tag::P).with_text(stream)); + body.push_child( + &Element::new(Tag::Blockquote) + .with_child(Element::new(Tag::Pre).with_text(&String::from_utf8_lossy(data))), ); } } } +#[derive(Debug, Default)] +struct ToC { + counter: usize, + headings: Vec<(String, Element)>, +} + +impl ToC { + fn push(&mut self, content: Element) -> Element { + self.counter += 1; + let anchor = format!("h{}", self.counter); + self.headings.push((anchor.clone(), content.clone())); + Element::new(Tag::H2) + .with_attribute("id", &format!("h{}", self.counter)) + .with_child(content) + } + + fn as_html(&self) -> Element { + let mut toc = + Element::new(Tag::Div).with_child(Element::new(Tag::H2).with_text("Table of contents")); + let mut list = Element::new(Tag::Ul); + for (anchor, content) in self.headings.iter() { + let entry = Element::new(Tag::Li).with_child( + Element::new(Tag::A) + .with_attribute("href", &format!("#{anchor}")) + .with_child(content.clone()), + ); + list.push_child(&entry); + } + toc.push_child(&list); + toc + } +} + #[derive(Debug, thiserror::Error)] pub enum RunLogError { #[error("programming error: missing field for run log {0}")]