github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/prow/spyglass/lenses/buildlog/buildlog.ts (about) 1 function showElem(elem: HTMLElement): void { 2 elem.className = 'shown'; 3 elem.innerHTML = ansiToHTML(elem.innerHTML); 4 } 5 6 // given a string containing ansi formatting directives, return a new one 7 // with designated regions of text marked with the appropriate color directives, 8 // and with all unknown directives stripped 9 function ansiToHTML(orig: string): string { 10 // Given a cmd (like "32" or "0;97"), some enclosed body text, and the original string, 11 // either return the body wrapped in an element to achieve the desired result, or the 12 // original string if nothing works. 13 function annotate(cmd: string, body: string, orig: string): string { 14 const code = +(cmd.replace('0;', '')); 15 if (code === 0) { 16 // reset 17 return body; 18 } else if (code === 1) { 19 // bold 20 return '<em>' + body + '</em>'; 21 } else if (30 <= code && code <= 37) { 22 // foreground color 23 return '<span class="ansi-' + (code - 30) + '">' + body + '</span>'; 24 } else if (90 <= code && code <= 97) { 25 // foreground color, bright 26 return '<span class="ansi-' + (code - 90 + 8) + '">' + body + '</span>'; 27 } 28 return body; // fallback: don't change anything 29 } 30 // Find commands, optionally followed by a bold command, with some content, then a reset command. 31 // Unpaired commands are *not* handled here, but they're very uncommon. 32 const filtered = orig.replace(/\033\[([0-9;]*)\w(\033\[1m)?([^\033]*?)\033\[0m/g, (match: string, cmd: string, bold: string, body: string, offset: number, str: string) => { 33 if (bold !== undefined) { 34 // normal code + bold 35 return '<em>' + annotate(cmd, body, str) + '</em>'; 36 } 37 return annotate(cmd, body, str); 38 }); 39 // Strip out anything left over. 40 return filtered.replace(/\033\[([0-9;]*\w)/g, (match: string, cmd: string, offset: number, str: string) => { 41 console.log('unhandled ansi code: ', cmd, "context:", filtered); 42 return ''; 43 }); 44 } 45 46 async function handleShowSkipped(this: HTMLDivElement, e: MouseEvent) { 47 // Don't do anything unless they actually clicked the button. 48 if (!(e.target instanceof HTMLButtonElement)) { 49 return; 50 } 51 const {artifact, offset, length, startLine} = this.dataset; 52 const content = await spyglass.request(JSON.stringify({ 53 artifact, offset: +offset!, length: +length!, startLine: +startLine!})); 54 this.innerHTML = ansiToHTML(content); 55 showElem(this); 56 57 // Remove the "show all" button if we no longer need it. 58 const log = document.getElementById(`${artifact}-content`)!; 59 const skipped = log.querySelectorAll<HTMLElement>(".show-skipped"); 60 if (skipped.length === 0) { 61 const button = document.querySelector('button.show-all-button')!; 62 button.parentNode!.removeChild(button); 63 } 64 spyglass.contentUpdated(); 65 } 66 67 async function handleShowAll(this: HTMLButtonElement) { 68 // Remove ourselves immediately. 69 if (this.parentElement) { 70 this.parentElement.removeChild(this); 71 } 72 73 const {artifact} = this.dataset; 74 const content = await spyglass.request(JSON.stringify({artifact, offset: 0, length: -1})); 75 document.getElementById(`${artifact}-content`)!.innerHTML = `<tbody class="shown">${ansiToHTML(content)}</tbody>`; 76 spyglass.contentUpdated(); 77 } 78 79 window.addEventListener('load', () => { 80 const shown = document.getElementsByClassName("shown"); 81 for (let i = 0; i < shown.length; i++) { 82 shown[i].innerHTML = ansiToHTML(shown[i].innerHTML); 83 } 84 85 for (const button of Array.from(document.querySelectorAll<HTMLDivElement>(".show-skipped"))) { 86 button.addEventListener('click', handleShowSkipped); 87 } 88 89 for (const button of Array.from(document.querySelectorAll<HTMLButtonElement>("button.show-all-button"))) { 90 button.addEventListener('click', handleShowAll); 91 } 92 });