diff --git a/Cargo.lock b/Cargo.lock index 08094df1..27147a30 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -37,6 +37,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + [[package]] name = "aliasable" version = "0.1.3" @@ -73,8 +82,8 @@ version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ - "anstyle", - "anstyle-parse", + "anstyle 1.0.10", + "anstyle-parse 0.2.6", "anstyle-query", "anstyle-wincon", "colorchoice", @@ -88,6 +97,19 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +[[package]] +name = "anstyle" +version = "1.0.11" +source = "git+https://github.com/GuillaumeGomez/anstyle?branch=improve-internals#d708cd4031ccbbe76d185426e30609740a06f88c" + +[[package]] +name = "anstyle-lossy" +version = "1.1.4" +source = "git+https://github.com/GuillaumeGomez/anstyle?branch=improve-internals#d708cd4031ccbbe76d185426e30609740a06f88c" +dependencies = [ + "anstyle 1.0.11", +] + [[package]] name = "anstyle-parse" version = "0.2.6" @@ -97,6 +119,14 @@ dependencies = [ "utf8parse", ] +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "git+https://github.com/GuillaumeGomez/anstyle?branch=improve-internals#d708cd4031ccbbe76d185426e30609740a06f88c" +dependencies = [ + "utf8parse", +] + [[package]] name = "anstyle-query" version = "1.1.2" @@ -106,13 +136,25 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "anstyle-svg" +version = "0.1.9" +source = "git+https://github.com/GuillaumeGomez/anstyle?branch=improve-internals#d708cd4031ccbbe76d185426e30609740a06f88c" +dependencies = [ + "anstyle 1.0.11", + "anstyle-lossy", + "anstyle-parse 0.2.7", + "html-escape", + "unicode-width 0.2.1", +] + [[package]] name = "anstyle-wincon" version = "3.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6680de5231bd6ee4c6191b8a1325daa282b415391ec9d3a37bd34f2060dc73fa" dependencies = [ - "anstyle", + "anstyle 1.0.10", "once_cell_polyfill", "windows-sys 0.59.0", ] @@ -381,7 +423,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89cc6392a1f72bbeb820d71f32108f61fdaf18bc526e1d23954168a67759ef51" dependencies = [ "anstream", - "anstyle", + "anstyle 1.0.10", "clap_lex", "strsim 0.11.1", ] @@ -854,7 +896,7 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" dependencies = [ - "unicode-width", + "unicode-width 0.1.9", ] [[package]] @@ -897,7 +939,7 @@ version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10463d9ff00a2a068db14231982f5132edebad0d7660cd956a1c30292dbcbfbd" dependencies = [ - "aho-corasick", + "aho-corasick 0.7.18", "bstr", "fnv", "log", @@ -976,6 +1018,15 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "html-escape" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d1ad449764d627e22bfd7cd5e8868264fc9236e07c752972b4080cd351cb476" +dependencies = [ + "utf8-width", +] + [[package]] name = "http" version = "0.2.9" @@ -1381,7 +1432,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" dependencies = [ - "regex-automata", + "regex-automata 0.1.10", ] [[package]] @@ -1990,13 +2041,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.6.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ - "aho-corasick", + "aho-corasick 1.1.3", "memchr", - "regex-syntax 0.6.27", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", ] [[package]] @@ -2008,6 +2060,17 @@ dependencies = [ "regex-syntax 0.6.27", ] +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick 1.1.3", + "memchr", + "regex-syntax 0.8.5", +] + [[package]] name = "regex-syntax" version = "0.6.27" @@ -2941,6 +3004,7 @@ dependencies = [ name = "triagebot" version = "0.1.0" dependencies = [ + "anstyle-svg", "anyhow", "async-trait", "bon", @@ -3096,6 +3160,12 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" +[[package]] +name = "unicode-width" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" + [[package]] name = "unicode-xid" version = "0.2.2" @@ -3126,6 +3196,12 @@ dependencies = [ "serde", ] +[[package]] +name = "utf8-width" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3" + [[package]] name = "utf8parse" version = "0.2.2" diff --git a/Cargo.toml b/Cargo.toml index fc3598ae..4f1bb71f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,6 +47,7 @@ clap = { version = "4", features = ["derive"] } hmac = "0.12.1" subtle = "2.6.1" sha2 = "0.10.9" +anstyle-svg = { git = "https://github.com/GuillaumeGomez/anstyle", branch = "improve-internals" } [dependencies.serde] version = "1" diff --git a/src/gha_logs.rs b/src/gha_logs.rs index cb1dfd5f..7c062687 100644 --- a/src/gha_logs.rs +++ b/src/gha_logs.rs @@ -3,12 +3,13 @@ use crate::handlers::Context; use anyhow::Context as _; use hyper::header::{CACHE_CONTROL, CONTENT_SECURITY_POLICY, CONTENT_TYPE}; use hyper::{Body, Response, StatusCode}; +use itertools::Itertools; +use regex::Regex; use std::collections::VecDeque; use std::str::FromStr; -use std::sync::Arc; +use std::sync::{Arc, LazyLock}; use uuid::Uuid; -pub const ANSI_UP_URL: &str = "/gha_logs/ansi_up@6.0.6.min.js"; pub const SUCCESS_URL: &str = "/gha_logs/success@1.svg"; pub const FAILURE_URL: &str = "/gha_logs/failure@1.svg"; @@ -22,10 +23,15 @@ pub struct GitHubActionLogsCache { pub struct CachedLog { job: WorkflowRunJob, - tree_roots: String, + tree_roots: Vec, logs: String, } +// The `>` at the beginning of the regex is to match the closing `
` to ensure we're only +// matching timestamps at the beginning of each line and not some random one present in the logs. +pub static TIMESTAMP_REGEX: LazyLock = + LazyLock::new(|| Regex::new(r">(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+Z)").unwrap()); + impl GitHubActionLogsCache { pub fn get(&mut self, key: &String) -> Option> { if let Some(pos) = self.entries.iter().position(|(k, _)| k == key) { @@ -183,24 +189,14 @@ async fn process_logs( // be matched before `library/stdarch` tree_roots.sort_by(|a, b| b.cmp(a)); - // Serialize to a JS(ON) array so we can escape them in the browser - let tree_roots = - serde_json::to_string(&tree_roots).context("unable to serialize the tree roots")?; - anyhow::Result::<_>::Ok((job, tree_roots)) }; let logs = async { - let logs = ctx - .github + ctx.github .raw_job_logs(&repo, log_id) .await - .context("unable to get the raw logs")?; - - let json_logs = - serde_json::to_string(&*logs).context("unable to JSON-ify the raw logs")?; - - anyhow::Result::<_>::Ok(json_logs) + .context("unable to get the raw logs") }; let (job_and_tree_roots, logs) = futures::join!(job_and_tree_roots, logs); @@ -217,7 +213,6 @@ async fn process_logs( ) }; - let nonce = Uuid::new_v4().to_hyphenated().to_string(); let job_name = &*job.name; let sha = &*job.head_sha; let short_sha = &job.head_sha[..7]; @@ -234,6 +229,93 @@ async fn process_logs( } }; + let tree_roots = tree_roots.iter().map(|p| regex::escape(p)).join("|"); + let regex = format!( + r#"(?[^a-zA-Z0-9.\\/])(?(?:[\\/]?(?:checkout[\\/])?(?(?:{tree_roots})(?:[\\/][a-zA-Z0-9_$\-.\\/]+)?))(?::(?[0-9]+):(?[0-9]+))?)"#, + ); + let gha_logs_path_regex = Regex::new(®ex).unwrap(); + + // 1. Remove UTF-8 useless BOM + let logs = logs.strip_prefix('\u{FEFF}').unwrap_or(logs.as_str()); + + // 2. Tranform the ANSI escape codes to HTML + let anstyle_svg::HtmlParts { body, style } = anstyle_svg::Term::new() + .use_html5(true) + .render_html_parts(logs); + + // 3. Add a self-referencial anchor to all timestamps at the start of the lines + let html = TIMESTAMP_REGEX.replace_all(&body, |ts: ®ex::Captures| { + let ts = &ts[0][1..]; + format!(">{ts}") + }); + + // 4. Add a anchor around every "##[error]" string + let mut error_counter = -1; + let html = html + .split("##[error]") + .fold(String::with_capacity(html.len()), |mut acc, part| { + // We only push the `` tag if it's not the first iteration. + if error_counter >= 0 { + acc.push_str(&format!( + "##[error]" + )); + } + acc.push_str(part); + error_counter += 1; + acc + }); + + // 4.b Add a span around every "##[warning]" string + let html = html.replace( + "##[warning]", + r#"##[warning]"#, + ); + + // 5. Add anchors around some paths + // Detailed examples of what the regex does is at https://regex101.com/r/vCnx9Y/2 + // + // But simply speaking the regex tries to find absolute (with `/checkout` prefix) and + // relative paths, the path must start with one of the repository top-level directory. + // We also try to retrieve the lines and cols if given (`:line:col`). + // + // Some examples of paths we want to find: + // - src/tools/test-float-parse/src/traits.rs:173:11 + // - /checkout/compiler/rustc_macros + // - /checkout/src/doc/rustdoc/src/advanced-features.md + // + // Any other paths, in particular if prefixed by `./` or `obj/` should not taken. + let html = gha_logs_path_regex.replace_all(&html, |capture: ®ex::Captures| { + let line = match capture.name("line") { + Some(line) => format!("#L{}", line.as_str()), + None => String::new(), + }; + let boundary = capture.name("boundary").map_or("", |m| m.as_str()); + let inner = capture.name("inner").map_or("", |m| m.as_str()); + let path = &capture["path"]; + format!(r#"{boundary}{inner}"#) + }); + + let nonce = Uuid::new_v4().to_hyphenated().to_string(); + let js = if error_counter >= 0 { + format!( + r#" +"#, + error_counter = error_counter - 1, + ) + } else { + String::new() + }; + let html = format!( r###" @@ -243,102 +325,35 @@ async fn process_logs( {job_name} - {owner}/{repo}@{short_sha} {icon_status} - - + +{html} +{js} "###, ); @@ -357,17 +372,6 @@ async fn process_logs( .body(Body::from(html))?); } -pub fn ansi_up_min_js() -> anyhow::Result, hyper::Error> { - const ANSI_UP_MIN_JS: &str = include_str!("gha_logs/ansi_up@6.0.6.min.js"); - - Ok(Response::builder() - .status(StatusCode::OK) - .header(CACHE_CONTROL, "public, max-age=15552000, immutable") - .header(CONTENT_TYPE, "text/javascript; charset=utf-8") - .body(Body::from(ANSI_UP_MIN_JS)) - .unwrap()) -} - pub fn success_svg() -> anyhow::Result, hyper::Error> { const SUCCESS_SVG: &str = include_str!("gha_logs/success.svg"); diff --git a/src/gha_logs/ansi_up@6.0.6.min.js b/src/gha_logs/ansi_up@6.0.6.min.js deleted file mode 100644 index eca73051..00000000 --- a/src/gha_logs/ansi_up@6.0.6.min.js +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: MIT -/** - * https://github.com/drudru/ansi_up - * - * (The MIT License) - * - * Copyright (c) 2011 github.com/drudru - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * 'Software'), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -var e,t,n,i,s=function(e,t){return Object.defineProperty?Object.defineProperty(e,"raw",{value:t}):e.raw=t,e};!function(e){e[e.EOS=0]="EOS",e[e.Text=1]="Text",e[e.Incomplete=2]="Incomplete",e[e.ESC=3]="ESC",e[e.Unknown=4]="Unknown",e[e.SGR=5]="SGR",e[e.OSCURL=6]="OSCURL"}(e||(e={}));class l{constructor(){this.VERSION="6.0.6",this.setup_palettes(),this._use_classes=!1,this.bold=!1,this.faint=!1,this.italic=!1,this.underline=!1,this.fg=this.bg=null,this._buffer="",this._url_allowlist={http:1,https:1},this._escape_html=!0,this.boldStyle="font-weight:bold",this.faintStyle="opacity:0.7",this.italicStyle="font-style:italic",this.underlineStyle="text-decoration:underline"}set use_classes(e){this._use_classes=e}get use_classes(){return this._use_classes}set url_allowlist(e){this._url_allowlist=e}get url_allowlist(){return this._url_allowlist}set escape_html(e){this._escape_html=e}get escape_html(){return this._escape_html}set boldStyle(e){this._boldStyle=e}get boldStyle(){return this._boldStyle}set faintStyle(e){this._faintStyle=e}get faintStyle(){return this._faintStyle}set italicStyle(e){this._italicStyle=e}get italicStyle(){return this._italicStyle}set underlineStyle(e){this._underlineStyle=e}get underlineStyle(){return this._underlineStyle}setup_palettes(){this.ansi_colors=[[{rgb:[0,0,0],class_name:"ansi-black"},{rgb:[187,0,0],class_name:"ansi-red"},{rgb:[0,187,0],class_name:"ansi-green"},{rgb:[187,187,0],class_name:"ansi-yellow"},{rgb:[0,0,187],class_name:"ansi-blue"},{rgb:[187,0,187],class_name:"ansi-magenta"},{rgb:[0,187,187],class_name:"ansi-cyan"},{rgb:[255,255,255],class_name:"ansi-white"}],[{rgb:[85,85,85],class_name:"ansi-bright-black"},{rgb:[255,85,85],class_name:"ansi-bright-red"},{rgb:[0,255,0],class_name:"ansi-bright-green"},{rgb:[255,255,85],class_name:"ansi-bright-yellow"},{rgb:[85,85,255],class_name:"ansi-bright-blue"},{rgb:[255,85,255],class_name:"ansi-bright-magenta"},{rgb:[85,255,255],class_name:"ansi-bright-cyan"},{rgb:[255,255,255],class_name:"ansi-bright-white"}]],this.palette_256=[],this.ansi_colors.forEach((e=>{e.forEach((e=>{this.palette_256.push(e)}))}));let e=[0,95,135,175,215,255];for(let t=0;t<6;++t)for(let n=0;n<6;++n)for(let i=0;i<6;++i){let s={rgb:[e[t],e[n],e[i]],class_name:"truecolor"};this.palette_256.push(s)}let t=8;for(let e=0;e<24;++e,t+=10){let e={rgb:[t,t,t],class_name:"truecolor"};this.palette_256.push(e)}}escape_txt_for_html(e){return this._escape_html?e.replace(/[&<>"']/gm,(e=>"&"===e?"&":"<"===e?"<":">"===e?">":'"'===e?""":"'"===e?"'":void 0)):e}append_buffer(e){var t=this._buffer+e;this._buffer=t}get_next_packet(){var l={kind:e.EOS,text:"",url:""},a=this._buffer.length;if(0==a)return l;var h=this._buffer.indexOf("");if(-1==h)return l.kind=e.Text,l.text=this._buffer,this._buffer="",l;if(h>0)return l.kind=e.Text,l.text=this._buffer.slice(0,h),this._buffer=this._buffer.slice(h),l;if(0==h){if(a<3)return l.kind=e.Incomplete,l;var f=this._buffer.charAt(1);if("["!=f&&"]"!=f&&"("!=f)return l.kind=e.ESC,l.text=this._buffer.slice(0,1),this._buffer=this._buffer.slice(1),l;if("["==f){this._csi_regex||(this._csi_regex=r(t||(t=s(["\n ^ # beginning of line\n #\n # First attempt\n (?: # legal sequence\n [ # CSI\n ([<-?]?) # private-mode char\n ([d;]*) # any digits or semicolons\n ([ -/]? # an intermediate modifier\n [@-~]) # the command\n )\n | # alternate (second attempt)\n (?: # illegal sequence\n [ # CSI\n [ -~]* # anything legal\n ([\0-:]) # anything illegal\n )\n "],["\n ^ # beginning of line\n #\n # First attempt\n (?: # legal sequence\n \\x1b\\[ # CSI\n ([\\x3c-\\x3f]?) # private-mode char\n ([\\d;]*) # any digits or semicolons\n ([\\x20-\\x2f]? # an intermediate modifier\n [\\x40-\\x7e]) # the command\n )\n | # alternate (second attempt)\n (?: # illegal sequence\n \\x1b\\[ # CSI\n [\\x20-\\x7e]* # anything legal\n ([\\x00-\\x1f:]) # anything illegal\n )\n "]))));let n=this._buffer.match(this._csi_regex);if(null===n)return l.kind=e.Incomplete,l;if(n[4])return l.kind=e.ESC,l.text=this._buffer.slice(0,1),this._buffer=this._buffer.slice(1),l;""!=n[1]||"m"!=n[3]?l.kind=e.Unknown:l.kind=e.SGR,l.text=n[2];var _=n[0].length;return this._buffer=this._buffer.slice(_),l}if("]"==f){if(a<4)return l.kind=e.Incomplete,l;if("8"!=this._buffer.charAt(2)||";"!=this._buffer.charAt(3))return l.kind=e.ESC,l.text=this._buffer.slice(0,1),this._buffer=this._buffer.slice(1),l;this._osc_st||(this._osc_st=function(e){let t=e.raw[0],n=/^\s+|\s+\n|\s*#[\s\S]*?\n|\n/gm,i=t.replace(n,"");return new RegExp(i,"g")}(n||(n=s(["\n (?: # legal sequence\n (\\) # ESC | # alternate\n () # BEL (what xterm did)\n )\n | # alternate (second attempt)\n ( # illegal sequence\n [\0-] # anything illegal\n | # alternate\n [\b-] # anything illegal\n | # alternate\n [-] # anything illegal\n )\n "],["\n (?: # legal sequence\n (\\x1b\\\\) # ESC \\\n | # alternate\n (\\x07) # BEL (what xterm did)\n )\n | # alternate (second attempt)\n ( # illegal sequence\n [\\x00-\\x06] # anything illegal\n | # alternate\n [\\x08-\\x1a] # anything illegal\n | # alternate\n [\\x1c-\\x1f] # anything illegal\n )\n "])))),this._osc_st.lastIndex=0;{let t=this._osc_st.exec(this._buffer);if(null===t)return l.kind=e.Incomplete,l;if(t[3])return l.kind=e.ESC,l.text=this._buffer.slice(0,1),this._buffer=this._buffer.slice(1),l}{let t=this._osc_st.exec(this._buffer);if(null===t)return l.kind=e.Incomplete,l;if(t[3])return l.kind=e.ESC,l.text=this._buffer.slice(0,1),this._buffer=this._buffer.slice(1),l}this._osc_regex||(this._osc_regex=r(i||(i=s(["\n ^ # beginning of line\n #\n ]8; # OSC Hyperlink\n [ -:<-~]* # params (excluding ;)\n ; # end of params\n ([!-~]{0,512}) # URL capture\n (?: # ST\n (?:\\) # ESC | # alternate\n (?:) # BEL (what xterm did)\n )\n ([ -~]+) # TEXT capture\n ]8;; # OSC Hyperlink End\n (?: # ST\n (?:\\) # ESC | # alternate\n (?:) # BEL (what xterm did)\n )\n "],["\n ^ # beginning of line\n #\n \\x1b\\]8; # OSC Hyperlink\n [\\x20-\\x3a\\x3c-\\x7e]* # params (excluding ;)\n ; # end of params\n ([\\x21-\\x7e]{0,512}) # URL capture\n (?: # ST\n (?:\\x1b\\\\) # ESC \\\n | # alternate\n (?:\\x07) # BEL (what xterm did)\n )\n ([\\x20-\\x7e]+) # TEXT capture\n \\x1b\\]8;; # OSC Hyperlink End\n (?: # ST\n (?:\\x1b\\\\) # ESC \\\n | # alternate\n (?:\\x07) # BEL (what xterm did)\n )\n "]))));let t=this._buffer.match(this._osc_regex);if(null===t)return l.kind=e.ESC,l.text=this._buffer.slice(0,1),this._buffer=this._buffer.slice(1),l;l.kind=e.OSCURL,l.url=t[1],l.text=t[2];_=t[0].length;return this._buffer=this._buffer.slice(_),l}if("("==f)return l.kind=e.Unknown,this._buffer=this._buffer.slice(3),l}}ansi_to_html(t){this.append_buffer(t);for(var n=[];;){var i=this.get_next_packet();if(i.kind==e.EOS||i.kind==e.Incomplete)break;i.kind!=e.ESC&&i.kind!=e.Unknown&&(i.kind==e.Text?n.push(this.transform_to_html(this.with_state(i))):i.kind==e.SGR?this.process_ansi(i):i.kind==e.OSCURL&&n.push(this.process_hyperlink(i)))}return n.join("")}with_state(e){return{bold:this.bold,faint:this.faint,italic:this.italic,underline:this.underline,fg:this.fg,bg:this.bg,text:e.text}}process_ansi(e){let t=e.text.split(";");for(;t.length>0;){let e=t.shift(),n=parseInt(e,10);if(isNaN(n)||0===n)this.fg=null,this.bg=null,this.bold=!1,this.faint=!1,this.italic=!1,this.underline=!1;else if(1===n)this.bold=!0;else if(2===n)this.faint=!0;else if(3===n)this.italic=!0;else if(4===n)this.underline=!0;else if(21===n)this.bold=!1;else if(22===n)this.faint=!1,this.bold=!1;else if(23===n)this.italic=!1;else if(24===n)this.underline=!1;else if(39===n)this.fg=null;else if(49===n)this.bg=null;else if(n>=30&&n<38)this.fg=this.ansi_colors[0][n-30];else if(n>=40&&n<48)this.bg=this.ansi_colors[0][n-40];else if(n>=90&&n<98)this.fg=this.ansi_colors[1][n-90];else if(n>=100&&n<108)this.bg=this.ansi_colors[1][n-100];else if((38===n||48===n)&&t.length>0){let e=38===n,i=t.shift();if("5"===i&&t.length>0){let n=parseInt(t.shift(),10);n>=0&&n<=255&&(e?this.fg=this.palette_256[n]:this.bg=this.palette_256[n])}if("2"===i&&t.length>2){let n=parseInt(t.shift(),10),i=parseInt(t.shift(),10),s=parseInt(t.shift(),10);if(n>=0&&n<=255&&i>=0&&i<=255&&s>=0&&s<=255){let t={rgb:[n,i,s],class_name:"truecolor"};e?this.fg=t:this.bg=t}}}}}transform_to_html(e){let t=e.text;if(0===t.length)return t;if(t=this.escape_txt_for_html(t),!(e.bold||e.italic||e.faint||e.underline||null!==e.fg||null!==e.bg))return t;let n=[],i=[],s=e.fg,l=e.bg;e.bold&&n.push(this._boldStyle),e.faint&&n.push(this._faintStyle),e.italic&&n.push(this._italicStyle),e.underline&&n.push(this._underlineStyle),this._use_classes?(s&&("truecolor"!==s.class_name?i.push(`${s.class_name}-fg`):n.push(`color:rgb(${s.rgb.join(",")})`)),l&&("truecolor"!==l.class_name?i.push(`${l.class_name}-bg`):n.push(`background-color:rgb(${l.rgb.join(",")})`))):(s&&n.push(`color:rgb(${s.rgb.join(",")})`),l&&n.push(`background-color:rgb(${l.rgb})`));let r="",a="";return i.length&&(r=` class="${i.join(" ")}"`),n.length&&(a=` style="${n.join(";")}"`),`${t}`}process_hyperlink(e){let t=e.url.split(":");return t.length<1?"":this._url_allowlist[t[0]]?`${this.escape_txt_for_html(e.text)}`:""}}function r(e,...t){let n=e.raw[0].replace(/^\s+|\s+\n|\s*#[\s\S]*?\n|\n/gm,"");return new RegExp(n)}export{l as AnsiUp};export default null; diff --git a/src/main.rs b/src/main.rs index ad7355c8..29267717 100644 --- a/src/main.rs +++ b/src/main.rs @@ -65,9 +65,6 @@ async fn serve_req( } } - if req.uri.path() == triagebot::gha_logs::ANSI_UP_URL { - return triagebot::gha_logs::ansi_up_min_js(); - } if req.uri.path() == triagebot::gha_logs::SUCCESS_URL { return triagebot::gha_logs::success_svg(); } diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 11921a8a..a07dd937 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -6,6 +6,7 @@ use crate::handlers::Context; use crate::team_data::TeamClient; use crate::zulip::client::ZulipClient; use octocrab::Octocrab; +use regex::Regex; use std::future::Future; use std::sync::Arc; use tokio::sync::RwLock;