|
1 | | -use std::collections::VecDeque; |
2 | 1 | use std::io::Read; |
3 | 2 |
|
4 | 3 | mod ansi; |
5 | 4 | mod perform; |
6 | 5 | mod renderer; |
7 | 6 |
|
8 | | -pub struct Handle<R> { |
9 | | - renderer: renderer::Renderer, |
| 7 | +pub struct Renderer<R> { |
| 8 | + inner: renderer::Renderer, |
10 | 9 | parser: vte::Parser, |
11 | 10 | bytes: R, |
12 | | - finished_line: Option<VecDeque<u8>>, |
13 | | - wrote_final_line: bool, |
| 11 | + state: State, |
| 12 | + title: String, |
14 | 13 | } |
15 | 14 |
|
16 | | -const FIRST_LINE: &str = r#"<!DOCTYPE html><html><body style="background:#111;color:#eee;"><body> |
17 | | -<pre style="word-wrap:break-word;white-space:pre-wrap;font-size:14px;font-size-adjust:none;text-size-adjust:none;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;-ms-text-size-adjust:100%;"></style><span>"#; |
| 15 | +#[rustfmt::skip] |
| 16 | +macro_rules! log_format { |
| 17 | + () => { |
| 18 | +r#"<html><head><style> |
| 19 | +body {{ |
| 20 | + background: #111; |
| 21 | + color: #eee; |
| 22 | +}} |
| 23 | +pre {{ |
| 24 | + word-wrap: break-word; |
| 25 | + white-space: pre-wrap; |
| 26 | + font-size: 14px; |
| 27 | + font-size-adjust: none; |
| 28 | + text-size-adjust: none; |
| 29 | + -webkit-text-size-adjust: 100%; |
| 30 | + -moz-text-size-adjust: 100%; |
| 31 | + -ms-text-size-adjust: 100%; |
| 32 | +}} |
| 33 | +</style><title>{}</title></head> |
| 34 | +<script> |
| 35 | +function scroll_to_ub() {{ |
| 36 | + var ub = document.getElementById("ub"); |
| 37 | + if (ub !== null) {{ |
| 38 | + ub.scrollIntoView(); |
| 39 | + }} |
| 40 | +}} |
| 41 | +</script> |
| 42 | +<body onload="scroll_to_ub()"> |
| 43 | +<pre style="text-align: center;">{}</pre> |
| 44 | +<pre>"# |
| 45 | + } |
| 46 | +} |
| 47 | + |
| 48 | +#[derive(Debug, Clone, Copy, PartialEq, Eq)] |
| 49 | +enum State { |
| 50 | + Fresh, |
| 51 | + Rendering, |
| 52 | + Done, |
| 53 | +} |
18 | 54 |
|
19 | | -impl<R: Read> Handle<R> { |
20 | | - pub fn new(bytes: R) -> Self { |
| 55 | +impl<R: Read> Renderer<R> { |
| 56 | + pub fn new(bytes: R, title: String) -> Self { |
21 | 57 | Self { |
22 | | - renderer: renderer::Renderer::new(String::new()), |
| 58 | + inner: renderer::Renderer::new(String::new()), |
23 | 59 | parser: vte::Parser::new(), |
24 | 60 | bytes, |
25 | | - finished_line: Some(FIRST_LINE.as_bytes().to_vec().into()), |
26 | | - wrote_final_line: false, |
| 61 | + state: State::Fresh, |
| 62 | + title, |
27 | 63 | } |
28 | 64 | } |
29 | 65 |
|
30 | 66 | pub fn is_empty(&self) -> bool { |
31 | | - self.wrote_final_line && self.finished_line.is_none() |
| 67 | + self.state == State::Done |
| 68 | + } |
| 69 | + |
| 70 | + fn first_line(&self) -> Vec<u8> { |
| 71 | + format!(log_format!(), self.title, self.title).into_bytes() |
32 | 72 | } |
33 | | -} |
34 | 73 |
|
35 | | -impl<R> Read for Handle<R> |
36 | | -where |
37 | | - R: Read, |
38 | | -{ |
39 | | - fn read(&mut self, buf: &mut [u8]) -> Result<usize, std::io::Error> { |
40 | | - if buf.is_empty() { |
41 | | - return Ok(0); |
| 74 | + pub fn next_line(&mut self) -> Result<Option<Vec<u8>>, std::io::Error> { |
| 75 | + match self.state { |
| 76 | + State::Done => return Ok(None), |
| 77 | + State::Fresh => { |
| 78 | + self.state = State::Rendering; |
| 79 | + return Ok(Some(self.first_line())); |
| 80 | + } |
| 81 | + State::Rendering => {} |
42 | 82 | } |
43 | | - while self.finished_line.is_none() && !self.wrote_final_line { |
| 83 | + loop { |
44 | 84 | let mut byte = [0u8]; |
45 | 85 | let n = self.bytes.read(&mut byte)?; |
46 | 86 | if n == 0 { |
47 | | - if let Some(line) = self.renderer.remove_oldest_row() { |
48 | | - self.finished_line = Some(line.into()); |
| 87 | + let line = if let Some(line) = self.inner.remove_oldest_row() { |
| 88 | + Some(line) |
49 | 89 | } else { |
| 90 | + self.state = State::Done; |
50 | 91 | let mut line = Vec::new(); |
51 | 92 | line.extend(b"</span></pre></body><style>"); |
52 | | - self.renderer.emit_css(&mut line)?; |
| 93 | + self.inner.emit_css(&mut line)?; |
53 | 94 | line.extend(b"</style></html>"); |
54 | | - self.finished_line = Some(line.into()); |
55 | | - self.wrote_final_line = true; |
56 | | - } |
57 | | - break; |
| 95 | + line.into() |
| 96 | + }; |
| 97 | + return Ok(line); |
58 | 98 | } |
59 | | - self.parser.advance(&mut self.renderer, byte[0]); |
60 | | - if let Some(mut line) = self.renderer.pop_completed_row() { |
| 99 | + self.parser.advance(&mut self.inner, byte[0]); |
| 100 | + if let Some(mut line) = self.inner.pop_completed_row() { |
61 | 101 | if line.is_empty() { |
62 | 102 | log::warn!("renderer produced an empty line!"); |
63 | | - line = vec![b'\n']; |
| 103 | + line = b"\n".to_vec(); |
64 | 104 | } |
65 | | - self.finished_line = Some(line.into()); |
| 105 | + return Ok(Some(line)); |
66 | 106 | } |
67 | 107 | } |
68 | | - let Some(line) = self.finished_line.as_mut() else { |
69 | | - return Ok(0); |
70 | | - }; |
71 | | - let n = buf.len().min(line.len()); |
72 | | - for (src, dst) in line.drain(..n).zip(buf.iter_mut().take(n)) { |
73 | | - *dst = src; |
74 | | - } |
75 | | - if line.is_empty() { |
76 | | - self.finished_line = None; |
77 | | - } |
78 | | - Ok(n) |
79 | 108 | } |
80 | 109 | } |
81 | | - |
82 | | -pub fn render(name: String, bytes: &[u8]) -> (String, String) { |
83 | | - let mut h = Handle { |
84 | | - renderer: renderer::Renderer::new(name), |
85 | | - parser: vte::Parser::new(), |
86 | | - bytes, |
87 | | - finished_line: None, |
88 | | - wrote_final_line: false, |
89 | | - }; |
90 | | - let mut html = Vec::new(); |
91 | | - h.read_to_end(&mut html).unwrap(); |
92 | | - (String::new(), String::from_utf8(html).unwrap()) |
93 | | -} |
|
0 commit comments